First draft
MindSift Multi-Arch Docker Build / Lint, Build & Unit Tests (push) Successful in 2m7s
MindSift Multi-Arch Docker Build / Build & Push Docker Image (push) Failing after 29s

This commit is contained in:
sandrodach committed 2026-10-24 16:56:44 +02:00
1 parent aba52f2b3c
commit 84bc6884b0
37 files changed
+6697 -27

No files matched your search

+368
View File
@@ -0,0 +1,368 @@
import {
APIKey,
DashboardStats,
Document,
DocumentBlock,
ListItem,
PriorityLevel,
SharedList,
Space,
Thought,
User,
} from './types';
const TOKEN_STORAGE_KEY = 'mindsift_jwt_token';
const OFFLINE_QUEUE_KEY = 'mindsift_offline_capture_queue';
export function getToken(): string | null {
return localStorage.getItem(TOKEN_STORAGE_KEY);
}
export function setToken(token: string): void {
localStorage.setItem(TOKEN_STORAGE_KEY, token);
}
export function clearToken(): void {
localStorage.removeItem(TOKEN_STORAGE_KEY);
}
export function triggerHaptic(pattern: number | number[] = 15): void {
try {
if (typeof navigator !== 'undefined' && 'vibrate' in navigator) {
navigator.vibrate(pattern);
}
} catch {
// Ignore on desktop browsers
}
}
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = getToken();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const res = await fetch(path, {
...options,
headers,
});
if (!res.ok) {
let errMsg = `HTTP ${res.status}`;
try {
const errData = await res.json();
if (errData.error) errMsg = errData.error;
} catch {
// ignore
}
throw new Error(errMsg);
}
return res.json();
}
export const api = {
// --- AUTH ---
async quickStart(): Promise<{ token: string; user: User }> {
const res = await request<{ token: string; user: User }>('/api/v1/auth/quickstart', {
method: 'POST',
});
setToken(res.token);
return res;
},
async login(email: string, password: string): Promise<{ token: string; user: User }> {
const res = await request<{ token: string; user: User }>('/api/v1/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
setToken(res.token);
return res;
},
async register(email: string, password: string, displayName: string): Promise<{ token: string; user: User }> {
const res = await request<{ token: string; user: User }>('/api/v1/auth/register', {
method: 'POST',
body: JSON.stringify({ email, password, displayName }),
});
setToken(res.token);
return res;
},
async getMe(): Promise<{ user: User; stats: DashboardStats }> {
return request<{ user: User; stats: DashboardStats }>('/api/v1/auth/me');
},
async getStats(): Promise<DashboardStats> {
return request<DashboardStats>('/api/v1/stats');
},
// --- INSTANT CAPTURE & OFFLINE SYNC ---
async captureThought(
content: string,
priority: PriorityLevel = 'normal',
source: 'manual' | 'voice' = 'manual',
dueDate = ''
): Promise<Thought> {
try {
const thought = await request<Thought>('/api/v1/inbox', {
method: 'POST',
body: JSON.stringify({ content, priority, source, dueDate }),
});
return thought;
} catch (err) {
// Queue locally if offline
const queue = getOfflineQueue();
const offlineThought: Thought = {
id: `th_offline_${Date.now()}`,
userId: 'offline',
content,
tags: (content.match(/#([a-zA-Z0-9äöüÄÖÜß_-]+)/g) || []).map((t) => t.slice(1).toLowerCase()),
priority,
source,
status: 'inbox',
isCompleted: false,
dueDate,
createdAt: new Date().toISOString(),
};
queue.push(offlineThought);
localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(queue));
return offlineThought;
}
},
async flushOfflineQueue(): Promise<number> {
const queue = getOfflineQueue();
if (queue.length === 0) return 0;
let synced = 0;
const remaining: Thought[] = [];
for (const item of queue) {
try {
await request<Thought>('/api/v1/inbox', {
method: 'POST',
body: JSON.stringify({
content: item.content,
priority: item.priority,
source: item.source,
dueDate: item.dueDate || '',
}),
});
synced++;
} catch {
remaining.push(item);
}
}
localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(remaining));
return synced;
},
// --- THOUGHTS, TRIAGE & TASKS ---
async getTriageDeck(): Promise<Thought[]> {
return request<Thought[]>('/api/v1/triage/deck');
},
async getThoughts(status = 'all'): Promise<Thought[]> {
return request<Thought[]>(`/api/v1/thoughts?status=${encodeURIComponent(status)}`);
},
async triageAction(
thoughtId: string,
action: 'keep' | 'discard' | 'restore',
options?: {
convertTo?: 'task' | 'document' | 'list_item';
targetListId?: string;
dueDate?: string;
}
): Promise<Thought> {
return request<Thought>(`/api/v1/triage/${thoughtId}`, {
method: 'PATCH',
body: JSON.stringify({
action,
convertTo: options?.convertTo || '',
targetListId: options?.targetListId || '',
dueDate: options?.dueDate || '',
}),
});
},
async getTasks(): Promise<Thought[]> {
return request<Thought[]>('/api/v1/tasks');
},
async updateThought(
thoughtId: string,
patch: { content?: string; isCompleted?: boolean; dueDate?: string; priority?: PriorityLevel }
): Promise<Thought> {
return request<Thought>(`/api/v1/thoughts/${thoughtId}`, {
method: 'PATCH',
body: JSON.stringify(patch),
});
},
async deleteThought(thoughtId: string): Promise<void> {
await request(`/api/v1/thoughts/${thoughtId}`, { method: 'DELETE' });
},
async purgeDiscarded(): Promise<{ purged: number }> {
return request<{ purged: number }>('/api/v1/thoughts/discarded/purge', { method: 'DELETE' });
},
// --- WORKSPACE DOCUMENTS ---
async getDocuments(): Promise<Document[]> {
return request<Document[]>('/api/v1/documents');
},
async getDocument(id: string): Promise<Document> {
return request<Document>(`/api/v1/documents/${id}`);
},
async createDocument(payload: {
title: string;
icon?: string;
blocks?: DocumentBlock[];
tags?: string[];
isPinned?: boolean;
}): Promise<Document> {
return request<Document>('/api/v1/documents', {
method: 'POST',
body: JSON.stringify(payload),
});
},
async updateDocument(
id: string,
patch: {
title?: string;
icon?: string;
blocks?: DocumentBlock[];
tags?: string[];
isPinned?: boolean;
}
): Promise<Document> {
return request<Document>(`/api/v1/documents/${id}`, {
method: 'PUT',
body: JSON.stringify(patch),
});
},
async deleteDocument(id: string): Promise<void> {
await request(`/api/v1/documents/${id}`, { method: 'DELETE' });
},
// --- SPACES & REALTIME LISTS ---
async getSpaces(): Promise<Space[]> {
return request<Space[]>('/api/v1/spaces');
},
async createSpace(name: string, icon: string, description: string): Promise<Space> {
return request<Space>('/api/v1/spaces', {
method: 'POST',
body: JSON.stringify({ name, icon, description }),
});
},
async joinSpace(inviteCode: string): Promise<Space> {
return request<Space>('/api/v1/spaces/join', {
method: 'POST',
body: JSON.stringify({ inviteCode }),
});
},
async getSpaceLists(spaceId: string): Promise<SharedList[]> {
return request<SharedList[]>(`/api/v1/spaces/${spaceId}/lists`);
},
async getAllLists(): Promise<SharedList[]> {
return request<SharedList[]>('/api/v1/lists');
},
async createList(spaceId: string, title: string, icon: string, listType: 'shopping' | 'checklist' | 'vacation'): Promise<SharedList> {
return request<SharedList>(`/api/v1/spaces/${spaceId}/lists`, {
method: 'POST',
body: JSON.stringify({ title, icon, listType }),
});
},
async deleteList(listId: string): Promise<void> {
await request(`/api/v1/lists/${listId}`, { method: 'DELETE' });
},
async createListItem(listId: string, content: string, category = '', quantity = ''): Promise<ListItem> {
return request<ListItem>(`/api/v1/lists/${listId}/items`, {
method: 'POST',
body: JSON.stringify({ content, category, quantity }),
});
},
async toggleListItem(listId: string, itemId: string, isCompleted: boolean): Promise<ListItem> {
return request<ListItem>(`/api/v1/lists/${listId}/items/${itemId}`, {
method: 'PATCH',
body: JSON.stringify({ isCompleted }),
});
},
async deleteListItem(listId: string, itemId: string): Promise<void> {
await request(`/api/v1/lists/${listId}/items/${itemId}`, { method: 'DELETE' });
},
async clearCompletedItems(listId: string): Promise<{ removed: number }> {
return request<{ removed: number }>(`/api/v1/lists/${listId}/clear-completed`, {
method: 'POST',
});
},
// --- API KEYS, WEBHOOKS & EXPORT ---
async getAPIKeys(): Promise<APIKey[]> {
return request<APIKey[]>('/api/v1/keys');
},
async createAPIKey(name: string): Promise<APIKey> {
return request<APIKey>('/api/v1/keys', {
method: 'POST',
body: JSON.stringify({ name }),
});
},
async deleteAPIKey(id: string): Promise<void> {
await request(`/api/v1/keys/${id}`, { method: 'DELETE' });
},
async testWebhookIngest(token: string, title: string, priority: PriorityLevel, tags: string[]): Promise<{ status: string; thought: Thought }> {
return request<{ status: string; thought: Thought }>(`/api/v1/webhooks/ingest?token=${encodeURIComponent(token)}`, {
method: 'POST',
body: JSON.stringify({ title, priority, tags }),
});
},
async searchAll(q: string): Promise<{ thoughts: Thought[]; documents: Document[] }> {
return request<{ thoughts: Thought[]; documents: Document[] }>(`/api/v1/search?q=${encodeURIComponent(q)}`);
},
async exportAll(): Promise<any> {
return request<any>('/api/v1/export');
},
};
export function getOfflineQueue(): Thought[] {
try {
const raw = localStorage.getItem(OFFLINE_QUEUE_KEY);
if (!raw) return [];
return JSON.parse(raw);
} catch {
return [];
}
}
export function buildWebSocketURL(spaceId: string): string {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const token = getToken() || '';
return `${protocol}//${window.location.host}/api/v1/ws/spaces/${encodeURIComponent(spaceId)}?token=${encodeURIComponent(token)}`;
}