@realtimex/email-automator 2.1.1 → 2.2.1
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/api/server.ts +0 -6
- package/api/src/config/index.ts +3 -0
- package/bin/email-automator-setup.js +2 -3
- package/bin/email-automator.js +23 -7
- package/package.json +1 -2
- package/src/App.tsx +0 -622
- package/src/components/AccountSettings.tsx +0 -310
- package/src/components/AccountSettingsPage.tsx +0 -390
- package/src/components/Configuration.tsx +0 -1345
- package/src/components/Dashboard.tsx +0 -940
- package/src/components/ErrorBoundary.tsx +0 -71
- package/src/components/LiveTerminal.tsx +0 -308
- package/src/components/LoadingSpinner.tsx +0 -39
- package/src/components/Login.tsx +0 -371
- package/src/components/Logo.tsx +0 -57
- package/src/components/SetupWizard.tsx +0 -388
- package/src/components/Toast.tsx +0 -109
- package/src/components/migration/MigrationBanner.tsx +0 -97
- package/src/components/migration/MigrationModal.tsx +0 -458
- package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
- package/src/components/mode-toggle.tsx +0 -24
- package/src/components/theme-provider.tsx +0 -72
- package/src/components/ui/alert.tsx +0 -66
- package/src/components/ui/button.tsx +0 -57
- package/src/components/ui/card.tsx +0 -75
- package/src/components/ui/dialog.tsx +0 -133
- package/src/components/ui/input.tsx +0 -22
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/otp-input.tsx +0 -184
- package/src/context/AppContext.tsx +0 -422
- package/src/context/MigrationContext.tsx +0 -53
- package/src/context/TerminalContext.tsx +0 -31
- package/src/core/actions.ts +0 -76
- package/src/core/auth.ts +0 -108
- package/src/core/intelligence.ts +0 -76
- package/src/core/processor.ts +0 -112
- package/src/hooks/useRealtimeEmails.ts +0 -111
- package/src/index.css +0 -140
- package/src/lib/api-config.ts +0 -42
- package/src/lib/api-old.ts +0 -228
- package/src/lib/api.ts +0 -421
- package/src/lib/migration-check.ts +0 -264
- package/src/lib/sounds.ts +0 -120
- package/src/lib/supabase-config.ts +0 -117
- package/src/lib/supabase.ts +0 -28
- package/src/lib/types.ts +0 -166
- package/src/lib/utils.ts +0 -6
- package/src/main.tsx +0 -10
package/src/lib/api-old.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { getSupabaseConfig } from './supabase-config';
|
|
2
|
-
|
|
3
|
-
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3002';
|
|
4
|
-
|
|
5
|
-
interface ApiOptions extends RequestInit {
|
|
6
|
-
auth?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface ApiResponse<T> {
|
|
10
|
-
data?: T;
|
|
11
|
-
error?: { code: string; message: string };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
class ApiClient {
|
|
15
|
-
private baseUrl: string;
|
|
16
|
-
private token: string | null = null;
|
|
17
|
-
|
|
18
|
-
constructor(baseUrl: string) {
|
|
19
|
-
this.baseUrl = baseUrl;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
setToken(token: string | null) {
|
|
23
|
-
this.token = token;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
private async request<T>(
|
|
27
|
-
endpoint: string,
|
|
28
|
-
options: ApiOptions = {}
|
|
29
|
-
): Promise<ApiResponse<T>> {
|
|
30
|
-
const { auth = true, ...fetchOptions } = options;
|
|
31
|
-
|
|
32
|
-
const headers: HeadersInit = {
|
|
33
|
-
'Content-Type': 'application/json',
|
|
34
|
-
...(options.headers || {}),
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
if (auth && this.token) {
|
|
38
|
-
(headers as Record<string, string>)['Authorization'] = `Bearer ${this.token}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
43
|
-
...fetchOptions,
|
|
44
|
-
headers,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
if (!response.ok) {
|
|
48
|
-
const errorData = await response.json().catch(() => ({}));
|
|
49
|
-
return {
|
|
50
|
-
error: {
|
|
51
|
-
code: errorData.error?.code || 'API_ERROR',
|
|
52
|
-
message: errorData.error?.message || `Request failed: ${response.status}`,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const data = await response.json();
|
|
58
|
-
return { data };
|
|
59
|
-
} catch (error) {
|
|
60
|
-
return {
|
|
61
|
-
error: {
|
|
62
|
-
code: 'NETWORK_ERROR',
|
|
63
|
-
message: error instanceof Error ? error.message : 'Network error',
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Auth endpoints
|
|
70
|
-
async getGmailAuthUrl() {
|
|
71
|
-
return this.request<{ url: string }>('/api/auth/gmail/url', { method: 'GET' });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async connectGmail(code: string) {
|
|
75
|
-
return this.request<{ success: boolean; account: any }>('/api/auth/gmail/callback', {
|
|
76
|
-
method: 'POST',
|
|
77
|
-
body: JSON.stringify({ code }),
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async startMicrosoftDeviceFlow() {
|
|
82
|
-
return this.request<{ userCode: string; verificationUri: string; message: string }>(
|
|
83
|
-
'/api/auth/microsoft/device-flow',
|
|
84
|
-
{ method: 'POST' }
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async getAccounts() {
|
|
89
|
-
return this.request<{ accounts: any[] }>('/api/auth/accounts');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async disconnectAccount(accountId: string) {
|
|
93
|
-
return this.request<{ success: boolean }>(`/api/auth/accounts/${accountId}`, {
|
|
94
|
-
method: 'DELETE',
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Sync endpoints
|
|
99
|
-
async triggerSync(accountId: string) {
|
|
100
|
-
return this.request<{ message: string }>('/api/sync', {
|
|
101
|
-
method: 'POST',
|
|
102
|
-
body: JSON.stringify({ accountId }),
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async syncAll() {
|
|
107
|
-
return this.request<{ message: string; accountCount: number }>('/api/sync/all', {
|
|
108
|
-
method: 'POST',
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async getSyncLogs(limit = 10) {
|
|
113
|
-
return this.request<{ logs: any[] }>(`/api/sync/logs?limit=${limit}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Email endpoints
|
|
117
|
-
async getEmails(params: {
|
|
118
|
-
limit?: number;
|
|
119
|
-
offset?: number;
|
|
120
|
-
category?: string;
|
|
121
|
-
search?: string;
|
|
122
|
-
} = {}) {
|
|
123
|
-
const query = new URLSearchParams();
|
|
124
|
-
if (params.limit) query.set('limit', params.limit.toString());
|
|
125
|
-
if (params.offset) query.set('offset', params.offset.toString());
|
|
126
|
-
if (params.category) query.set('category', params.category);
|
|
127
|
-
if (params.search) query.set('search', params.search);
|
|
128
|
-
|
|
129
|
-
return this.request<{ emails: any[]; total: number }>(`/api/emails?${query}`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async getEmail(emailId: string) {
|
|
133
|
-
return this.request<{ email: any }>(`/api/emails/${emailId}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async getCategorySummary() {
|
|
137
|
-
return this.request<{ categories: Record<string, number> }>('/api/emails/summary/categories');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Action endpoints
|
|
141
|
-
async executeAction(emailId: string, action: string, draftContent?: string) {
|
|
142
|
-
return this.request<{ success: boolean; details?: string }>('/api/actions/execute', {
|
|
143
|
-
method: 'POST',
|
|
144
|
-
body: JSON.stringify({ emailId, action, draftContent }),
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async generateDraft(emailId: string, instructions?: string) {
|
|
149
|
-
return this.request<{ draft: string }>(`/api/actions/draft/${emailId}`, {
|
|
150
|
-
method: 'POST',
|
|
151
|
-
body: JSON.stringify({ instructions }),
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async bulkAction(emailIds: string[], action: string) {
|
|
156
|
-
return this.request<{ success: number; failed: number }>('/api/actions/bulk', {
|
|
157
|
-
method: 'POST',
|
|
158
|
-
body: JSON.stringify({ emailIds, action }),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Rules endpoints
|
|
163
|
-
async getRules() {
|
|
164
|
-
return this.request<{ rules: any[] }>('/api/rules');
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async createRule(rule: { name: string; condition: any; action: string; is_enabled?: boolean }) {
|
|
168
|
-
return this.request<{ rule: any }>('/api/rules', {
|
|
169
|
-
method: 'POST',
|
|
170
|
-
body: JSON.stringify(rule),
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async updateRule(ruleId: string, updates: any) {
|
|
175
|
-
return this.request<{ rule: any }>(`/api/rules/${ruleId}`, {
|
|
176
|
-
method: 'PATCH',
|
|
177
|
-
body: JSON.stringify(updates),
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async deleteRule(ruleId: string) {
|
|
182
|
-
return this.request<{ success: boolean }>(`/api/rules/${ruleId}`, {
|
|
183
|
-
method: 'DELETE',
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async toggleRule(ruleId: string) {
|
|
188
|
-
return this.request<{ rule: any }>(`/api/rules/${ruleId}/toggle`, {
|
|
189
|
-
method: 'POST',
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Settings endpoints
|
|
194
|
-
async getSettings() {
|
|
195
|
-
return this.request<{ settings: any }>('/api/settings');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async updateSettings(settings: any) {
|
|
199
|
-
return this.request<{ settings: any }>('/api/settings', {
|
|
200
|
-
method: 'PATCH',
|
|
201
|
-
body: JSON.stringify(settings),
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async getStats() {
|
|
206
|
-
return this.request<{ stats: any }>('/api/settings/stats');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Health check
|
|
210
|
-
async healthCheck() {
|
|
211
|
-
return this.request<{ status: string; services: any }>('/api/health', { auth: false });
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export const api = new ApiClient(API_BASE_URL);
|
|
216
|
-
|
|
217
|
-
// Helper to initialize API with auth token from Supabase session
|
|
218
|
-
export async function initializeApi(supabase: any) {
|
|
219
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
220
|
-
if (session?.access_token) {
|
|
221
|
-
api.setToken(session.access_token);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Listen for auth changes
|
|
225
|
-
supabase.auth.onAuthStateChange((_event: string, session: any) => {
|
|
226
|
-
api.setToken(session?.access_token || null);
|
|
227
|
-
});
|
|
228
|
-
}
|
package/src/lib/api.ts
DELETED
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hybrid API Client for Email Automator
|
|
3
|
-
*
|
|
4
|
-
* Architecture:
|
|
5
|
-
* - Edge Functions: Auth, OAuth, Database operations (via Supabase)
|
|
6
|
-
* - Express API (Local App): Email sync, AI processing
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { getApiConfig } from './api-config';
|
|
10
|
-
import { EmailAccount } from './types';
|
|
11
|
-
|
|
12
|
-
interface ApiOptions extends RequestInit {
|
|
13
|
-
auth?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface ApiResponse<T> {
|
|
17
|
-
data?: T;
|
|
18
|
-
error?: { code?: string; message: string } | string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
class HybridApiClient {
|
|
22
|
-
private edgeFunctionsUrl: string;
|
|
23
|
-
private expressApiUrl: string;
|
|
24
|
-
private anonKey: string;
|
|
25
|
-
private token: string | null = null;
|
|
26
|
-
private supabaseClient: any = null;
|
|
27
|
-
|
|
28
|
-
constructor() {
|
|
29
|
-
const config = getApiConfig();
|
|
30
|
-
this.edgeFunctionsUrl = config.edgeFunctionsUrl;
|
|
31
|
-
this.expressApiUrl = config.expressApiUrl;
|
|
32
|
-
this.anonKey = config.anonKey;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
setSupabaseClient(client: any) {
|
|
36
|
-
this.supabaseClient = client;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
setToken(token: string | null) {
|
|
40
|
-
if (token) {
|
|
41
|
-
console.debug('[HybridApiClient] Token updated');
|
|
42
|
-
} else {
|
|
43
|
-
console.debug('[HybridApiClient] Token cleared');
|
|
44
|
-
}
|
|
45
|
-
this.token = token;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private async request<T>(
|
|
49
|
-
baseUrl: string,
|
|
50
|
-
endpoint: string,
|
|
51
|
-
options: ApiOptions = {}
|
|
52
|
-
): Promise<ApiResponse<T>> {
|
|
53
|
-
const { auth = true, ...fetchOptions } = options;
|
|
54
|
-
|
|
55
|
-
const headers: HeadersInit = {
|
|
56
|
-
'Content-Type': 'application/json',
|
|
57
|
-
...(options.headers || {}),
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Fallback: try to get token from supabaseClient if missing
|
|
61
|
-
if (auth && !this.token && this.supabaseClient) {
|
|
62
|
-
const { data: { session } } = await this.supabaseClient.auth.getSession();
|
|
63
|
-
if (session?.access_token) {
|
|
64
|
-
this.token = session.access_token;
|
|
65
|
-
console.debug('[HybridApiClient] Recovered token from session');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (auth && !this.token) {
|
|
70
|
-
console.warn(`[HybridApiClient] Attempted protected request to ${endpoint} without token`);
|
|
71
|
-
return {
|
|
72
|
-
error: {
|
|
73
|
-
code: 'AUTH_REQUIRED',
|
|
74
|
-
message: 'You must be logged in to perform this action',
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (auth && this.token) {
|
|
80
|
-
(headers as Record<string, string>)['Authorization'] = `Bearer ${this.token}`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
console.debug(`[HybridApiClient] ${options.method || 'GET'} ${baseUrl}${endpoint}`);
|
|
85
|
-
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
86
|
-
...fetchOptions,
|
|
87
|
-
headers,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
if (!response.ok) {
|
|
91
|
-
const errorData = await response.json().catch(() => ({}));
|
|
92
|
-
const errorMessage = typeof errorData.error === 'string'
|
|
93
|
-
? errorData.error
|
|
94
|
-
: errorData.error?.message || `Request failed: ${response.status}`;
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
error: {
|
|
98
|
-
code: errorData.error?.code || 'API_ERROR',
|
|
99
|
-
message: errorMessage,
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const data = await response.json();
|
|
105
|
-
return { data };
|
|
106
|
-
} catch (error) {
|
|
107
|
-
return {
|
|
108
|
-
error: {
|
|
109
|
-
code: 'NETWORK_ERROR',
|
|
110
|
-
message: error instanceof Error ? error.message : 'Network error',
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Edge Functions requests
|
|
117
|
-
private edgeRequest<T>(endpoint: string, options?: ApiOptions) {
|
|
118
|
-
// Supabase Gateway requires 'apikey' header for all requests
|
|
119
|
-
const headers = {
|
|
120
|
-
...(options?.headers || {}),
|
|
121
|
-
apikey: this.anonKey,
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
return this.request<T>(this.edgeFunctionsUrl, endpoint, {
|
|
125
|
-
...options,
|
|
126
|
-
headers,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Express API requests - include Supabase config for backend to use
|
|
131
|
-
private expressRequest<T>(endpoint: string, options?: ApiOptions) {
|
|
132
|
-
const config = getApiConfig();
|
|
133
|
-
const headers = {
|
|
134
|
-
...(options?.headers || {}),
|
|
135
|
-
// Pass Supabase config so backend can connect dynamically
|
|
136
|
-
'X-Supabase-Url': config.edgeFunctionsUrl.replace('/functions/v1', ''),
|
|
137
|
-
'X-Supabase-Anon-Key': config.anonKey,
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
return this.request<T>(this.expressApiUrl, endpoint, {
|
|
141
|
-
...options,
|
|
142
|
-
headers,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ============================================================================
|
|
147
|
-
// AUTH & OAUTH ENDPOINTS (Edge Functions)
|
|
148
|
-
// ============================================================================
|
|
149
|
-
|
|
150
|
-
async getGmailAuthUrl() {
|
|
151
|
-
return this.edgeRequest<{ url: string }>('/auth-gmail?action=url', {
|
|
152
|
-
method: 'GET',
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async connectGmail(code: string) {
|
|
157
|
-
return this.edgeRequest<{ success: boolean; account: any }>('/auth-gmail', {
|
|
158
|
-
method: 'POST',
|
|
159
|
-
body: JSON.stringify({ code }),
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async startMicrosoftDeviceFlow() {
|
|
164
|
-
return this.edgeRequest<{
|
|
165
|
-
userCode: string;
|
|
166
|
-
verificationUri: string;
|
|
167
|
-
message: string;
|
|
168
|
-
deviceCode: string;
|
|
169
|
-
expiresIn: number;
|
|
170
|
-
interval: number;
|
|
171
|
-
}>('/auth-microsoft?action=device-flow', {
|
|
172
|
-
method: 'POST',
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async pollMicrosoftDeviceCode(deviceCode: string) {
|
|
177
|
-
return this.edgeRequest<{
|
|
178
|
-
status: 'pending' | 'completed';
|
|
179
|
-
account?: any
|
|
180
|
-
}>('/auth-microsoft?action=poll', {
|
|
181
|
-
method: 'POST',
|
|
182
|
-
body: JSON.stringify({ deviceCode }),
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ============================================================================
|
|
187
|
-
// ACCOUNTS ENDPOINTS (Edge Functions)
|
|
188
|
-
// ============================================================================
|
|
189
|
-
|
|
190
|
-
async getAccounts() {
|
|
191
|
-
return this.edgeRequest<{ accounts: any[] }>('/api-v1-accounts');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async disconnectAccount(accountId: string) {
|
|
195
|
-
return this.edgeRequest<{ success: boolean }>(`/api-v1-accounts/${accountId}`, {
|
|
196
|
-
method: 'DELETE',
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async updateAccount(accountId: string, updates: Partial<EmailAccount>) {
|
|
201
|
-
return this.edgeRequest<{ account: EmailAccount }>(`/api-v1-accounts/${accountId}`, {
|
|
202
|
-
method: 'PATCH',
|
|
203
|
-
body: JSON.stringify(updates),
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ============================================================================
|
|
208
|
-
// EMAILS ENDPOINTS (Edge Functions)
|
|
209
|
-
// ============================================================================
|
|
210
|
-
|
|
211
|
-
async getEmails(params: {
|
|
212
|
-
limit?: number;
|
|
213
|
-
offset?: number;
|
|
214
|
-
category?: string;
|
|
215
|
-
search?: string;
|
|
216
|
-
} = {}) {
|
|
217
|
-
const query = new URLSearchParams();
|
|
218
|
-
if (params.limit) query.set('limit', params.limit.toString());
|
|
219
|
-
if (params.offset) query.set('offset', params.offset.toString());
|
|
220
|
-
if (params.category) query.set('category', params.category);
|
|
221
|
-
if (params.search) query.set('search', params.search);
|
|
222
|
-
|
|
223
|
-
return this.edgeRequest<{ emails: any[]; total: number }>(`/api-v1-emails?${query}`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async getEmail(emailId: string) {
|
|
227
|
-
return this.edgeRequest<{ email: any }>(`/api-v1-emails/${emailId}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async getEmailEvents(emailId: string) {
|
|
231
|
-
return this.edgeRequest<{ events: any[] }>(`/api-v1-emails/${emailId}/events`);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async deleteEmail(emailId: string) {
|
|
235
|
-
return this.edgeRequest<{ success: boolean }>(`/api-v1-emails/${emailId}`, {
|
|
236
|
-
method: 'DELETE',
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async getCategorySummary() {
|
|
241
|
-
return this.edgeRequest<{ categories: Record<string, number> }>('/api-v1-emails/summary/categories');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ============================================================================
|
|
245
|
-
// RULES ENDPOINTS (Express API - Local App)
|
|
246
|
-
// ============================================================================
|
|
247
|
-
|
|
248
|
-
async getRules() {
|
|
249
|
-
return this.expressRequest<{ rules: any[] }>('/api/rules');
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async createRule(rule: { name: string; condition: any; action: string; is_enabled?: boolean, instructions?: string, attachments?: any[] }) {
|
|
253
|
-
return this.expressRequest<{ rule: any }>('/api/rules', {
|
|
254
|
-
method: 'POST',
|
|
255
|
-
body: JSON.stringify(rule),
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async updateRule(ruleId: string, updates: any) {
|
|
260
|
-
return this.expressRequest<{ rule: any }>(`/api/rules/${ruleId}`, {
|
|
261
|
-
method: 'PATCH',
|
|
262
|
-
body: JSON.stringify(updates),
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async deleteRule(ruleId: string) {
|
|
267
|
-
return this.expressRequest<{ success: boolean }>(`/api/rules/${ruleId}`, {
|
|
268
|
-
method: 'DELETE',
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async toggleRule(ruleId: string) {
|
|
273
|
-
return this.expressRequest<{ rule: any }>(`/api/rules/${ruleId}/toggle`, {
|
|
274
|
-
method: 'POST',
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ============================================================================
|
|
279
|
-
// SETTINGS ENDPOINTS (Edge Functions)
|
|
280
|
-
// ============================================================================
|
|
281
|
-
|
|
282
|
-
async getSettings() {
|
|
283
|
-
return this.edgeRequest<{ settings: any }>('/api-v1-settings');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async getRunEvents(runId: string) {
|
|
287
|
-
return this.edgeRequest<{ events: any[] }>(`/api-v1-settings/logs/${runId}/events`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async updateSettings(settings: any) {
|
|
291
|
-
return this.edgeRequest<{ settings: any }>('/api-v1-settings', {
|
|
292
|
-
method: 'PATCH',
|
|
293
|
-
body: JSON.stringify(settings),
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async getStats() {
|
|
298
|
-
return this.edgeRequest<{ stats: any }>('/api-v1-settings/stats');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async testLlm(config: { llm_model: string | null; llm_base_url: string | null; llm_api_key: string | null }) {
|
|
302
|
-
return this.expressRequest<{ success: boolean; message: string }>('/api/settings/test-llm', {
|
|
303
|
-
method: 'POST',
|
|
304
|
-
body: JSON.stringify(config),
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// ============================================================================
|
|
309
|
-
// SYNC ENDPOINTS (Express API - Local App)
|
|
310
|
-
// ============================================================================
|
|
311
|
-
|
|
312
|
-
async triggerSync(accountId: string) {
|
|
313
|
-
return this.expressRequest<{ message: string }>('/api/sync', {
|
|
314
|
-
method: 'POST',
|
|
315
|
-
body: JSON.stringify({ accountId }),
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async syncAll() {
|
|
320
|
-
return this.expressRequest<{ message: string; accountCount: number }>('/api/sync/all', {
|
|
321
|
-
method: 'POST',
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
async getSyncLogs(limit = 10) {
|
|
326
|
-
return this.expressRequest<{ logs: any[] }>(`/api/sync/logs?limit=${limit}`);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ============================================================================
|
|
330
|
-
// ACTION ENDPOINTS (Express API - Local App)
|
|
331
|
-
// ============================================================================
|
|
332
|
-
|
|
333
|
-
async executeAction(emailId: string, action: string, draftContent?: string) {
|
|
334
|
-
return this.expressRequest<{ success: boolean; details?: string }>('/api/actions/execute', {
|
|
335
|
-
method: 'POST',
|
|
336
|
-
body: JSON.stringify({ emailId, action, draftContent }),
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async generateDraft(emailId: string, instructions?: string) {
|
|
341
|
-
return this.expressRequest<{ draft: string }>(`/api/actions/draft/${emailId}`, {
|
|
342
|
-
method: 'POST',
|
|
343
|
-
body: JSON.stringify({ instructions }),
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
async bulkAction(emailIds: string[], action: string) {
|
|
348
|
-
return this.expressRequest<{ success: number; failed: number }>('/api/actions/bulk', {
|
|
349
|
-
method: 'POST',
|
|
350
|
-
body: JSON.stringify({ emailIds, action }),
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ============================================================================
|
|
355
|
-
// HEALTH CHECK (Express API)
|
|
356
|
-
// ============================================================================
|
|
357
|
-
|
|
358
|
-
async healthCheck() {
|
|
359
|
-
return this.expressRequest<{ status: string; services: any }>('/api/health', {
|
|
360
|
-
auth: false
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// ============================================================================
|
|
365
|
-
// PROFILE & SECURITY (Edge Functions / Supabase Auth)
|
|
366
|
-
// ============================================================================
|
|
367
|
-
|
|
368
|
-
async getProfile() {
|
|
369
|
-
if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
|
|
370
|
-
const { data: { user } } = await this.supabaseClient.auth.getUser();
|
|
371
|
-
if (!user) return { error: 'Not authenticated' };
|
|
372
|
-
|
|
373
|
-
const { data, error } = await this.supabaseClient
|
|
374
|
-
.from('profiles')
|
|
375
|
-
.select('*')
|
|
376
|
-
.eq('id', user.id)
|
|
377
|
-
.single();
|
|
378
|
-
|
|
379
|
-
return { data, error };
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async updateProfile(updates: { first_name?: string; last_name?: string; avatar_url?: string }) {
|
|
383
|
-
if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
|
|
384
|
-
const { data: { user } } = await this.supabaseClient.auth.getUser();
|
|
385
|
-
if (!user) return { error: 'Not authenticated' };
|
|
386
|
-
|
|
387
|
-
const { data, error } = await this.supabaseClient
|
|
388
|
-
.from('profiles')
|
|
389
|
-
.update({
|
|
390
|
-
...updates,
|
|
391
|
-
updated_at: new Date().toISOString()
|
|
392
|
-
})
|
|
393
|
-
.eq('id', user.id)
|
|
394
|
-
.select()
|
|
395
|
-
.single();
|
|
396
|
-
|
|
397
|
-
return { data, error };
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async changePassword(password: string) {
|
|
401
|
-
if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
|
|
402
|
-
const { error } = await this.supabaseClient.auth.updateUser({ password });
|
|
403
|
-
return { success: !error, error };
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
export const api = new HybridApiClient();
|
|
408
|
-
|
|
409
|
-
// Helper to initialize API with auth token from Supabase session
|
|
410
|
-
export async function initializeApi(supabase: any) {
|
|
411
|
-
api.setSupabaseClient(supabase);
|
|
412
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
413
|
-
if (session?.access_token) {
|
|
414
|
-
api.setToken(session.access_token);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Listen for auth changes
|
|
418
|
-
supabase.auth.onAuthStateChange((_event: string, session: any) => {
|
|
419
|
-
api.setToken(session?.access_token || null);
|
|
420
|
-
});
|
|
421
|
-
}
|