From 9646da833bc3d94564c10649b62a378d0190471e Mon Sep 17 00:00:00 2001 From: blackhao <13851610112@163.com> Date: Wed, 10 Dec 2025 20:12:21 -0600 Subject: user data --- frontend/src/store/authStore.ts | 157 ++++++++++++++++++++++++++++++++++++++++ frontend/src/store/flowStore.ts | 42 ++++++++--- 2 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 frontend/src/store/authStore.ts (limited to 'frontend/src/store') diff --git a/frontend/src/store/authStore.ts b/frontend/src/store/authStore.ts new file mode 100644 index 0000000..652256c --- /dev/null +++ b/frontend/src/store/authStore.ts @@ -0,0 +1,157 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +const API_BASE = 'http://localhost:8000'; + +interface UserInfo { + id: number; + username: string; + email: string; + created_at: string; +} + +interface AuthState { + token: string | null; + user: UserInfo | null; + isAuthenticated: boolean; + isLoading: boolean; + error: string | null; + + // Actions + login: (username: string, password: string) => Promise; + register: (username: string, email: string, password: string) => Promise; + logout: () => void; + checkAuth: () => Promise; + clearError: () => void; + getAuthHeader: () => { Authorization: string } | {}; +} + +export const useAuthStore = create()( + persist( + (set, get) => ({ + token: null, + user: null, + isAuthenticated: false, + isLoading: false, + error: null, + + login: async (username: string, password: string) => { + set({ isLoading: true, error: null }); + + try { + // Use JSON login endpoint + const res = await fetch(`${API_BASE}/api/auth/login/json`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData.detail || 'Login failed'); + } + + const data = await res.json(); + set({ + token: data.access_token, + isAuthenticated: true, + isLoading: false + }); + + // Fetch user info + await get().checkAuth(); + } catch (err) { + set({ + isLoading: false, + error: (err as Error).message, + isAuthenticated: false, + token: null, + user: null + }); + throw err; + } + }, + + register: async (username: string, email: string, password: string) => { + set({ isLoading: true, error: null }); + + try { + const res = await fetch(`${API_BASE}/api/auth/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, email, password }), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData.detail || 'Registration failed'); + } + + set({ isLoading: false }); + } catch (err) { + set({ isLoading: false, error: (err as Error).message }); + throw err; + } + }, + + logout: () => { + set({ + token: null, + user: null, + isAuthenticated: false, + error: null + }); + }, + + checkAuth: async () => { + const token = get().token; + if (!token) { + set({ isAuthenticated: false, user: null }); + return false; + } + + try { + const res = await fetch(`${API_BASE}/api/auth/me`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (res.ok) { + const user = await res.json(); + set({ user, isAuthenticated: true }); + return true; + } else { + // Token is invalid + set({ token: null, user: null, isAuthenticated: false }); + return false; + } + } catch { + set({ token: null, user: null, isAuthenticated: false }); + return false; + } + }, + + clearError: () => { + set({ error: null }); + }, + + getAuthHeader: () => { + const token = get().token; + if (token) { + return { Authorization: `Bearer ${token}` }; + } + return {}; + }, + }), + { + name: 'contextflow-auth', + partialize: (state) => ({ + token: state.token, + user: state.user, + isAuthenticated: state.isAuthenticated + }), + } + ) +); + +export default useAuthStore; + diff --git a/frontend/src/store/flowStore.ts b/frontend/src/store/flowStore.ts index 498937e..de23c95 100644 --- a/frontend/src/store/flowStore.ts +++ b/frontend/src/store/flowStore.ts @@ -250,11 +250,35 @@ const getStableColor = (str: string) => { return `hsl(${hue}, 70%, 60%)`; }; +import { useAuthStore } from './authStore'; + const API_BASE = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000'; -const DEFAULT_USER = 'test'; +const DEFAULT_USER = 'test'; // Fallback for unauthenticated requests + +// Get current username directly from authStore +const getCurrentUser = () => { + const authState = useAuthStore.getState(); + return authState.user?.username || DEFAULT_USER; +}; + +// Get auth headers directly from authStore +const getAuthHeaders = (): Record => { + const authState = useAuthStore.getState(); + if (authState.token) { + return { Authorization: `Bearer ${authState.token}` }; + } + return {}; +}; const jsonFetch = async (url: string, options?: RequestInit): Promise => { - const res = await fetch(url, options); + const authHeaders = getAuthHeaders(); + const res = await fetch(url, { + ...options, + headers: { + ...options?.headers, + ...authHeaders, + }, + }); if (!res.ok) { const detail = await res.text(); throw new Error(detail || `Request failed: ${res.status}`); @@ -1474,7 +1498,7 @@ const useFlowStore = create((set, get) => { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - user: DEFAULT_USER, + user: getCurrentUser(), path, content: payload, }), @@ -1485,14 +1509,14 @@ const useFlowStore = create((set, get) => { readBlueprintFile: async (path: string): Promise => { const res = await jsonFetch<{ content: BlueprintDocument }>( - `${API_BASE}/api/projects/file?user=${encodeURIComponent(DEFAULT_USER)}&path=${encodeURIComponent(path)}` + `${API_BASE}/api/projects/file?user=${encodeURIComponent(getCurrentUser())}&path=${encodeURIComponent(path)}` ); return validateBlueprint(res.content); }, refreshProjectTree: async () => { const tree = await jsonFetch( - `${API_BASE}/api/projects/tree?user=${encodeURIComponent(DEFAULT_USER)}` + `${API_BASE}/api/projects/tree?user=${encodeURIComponent(getCurrentUser())}` ); set({ projectTree: tree }); return tree; @@ -1557,7 +1581,7 @@ const useFlowStore = create((set, get) => { loadArchivedNodes: async () => { const res = await jsonFetch<{ archived: ArchivedNode[] }>( - `${API_BASE}/api/projects/archived?user=${encodeURIComponent(DEFAULT_USER)}` + `${API_BASE}/api/projects/archived?user=${encodeURIComponent(getCurrentUser())}` ); set({ archivedNodes: res.archived || [] }); }, @@ -1574,7 +1598,7 @@ const useFlowStore = create((set, get) => { // Files management refreshFiles: async () => { const res = await jsonFetch<{ files: FileMeta[] }>( - `${API_BASE}/api/files?user=${encodeURIComponent(DEFAULT_USER)}` + `${API_BASE}/api/files?user=${encodeURIComponent(getCurrentUser())}` ); set({ files: res.files || [] }); }, @@ -1592,7 +1616,7 @@ const useFlowStore = create((set, get) => { form.append('purpose', purpose); } try { - const res = await fetch(`${API_BASE}/api/files/upload?user=${encodeURIComponent(DEFAULT_USER)}`, { + const res = await fetch(`${API_BASE}/api/files/upload?user=${encodeURIComponent(getCurrentUser())}`, { method: 'POST', body: form, }); @@ -1611,7 +1635,7 @@ const useFlowStore = create((set, get) => { }, deleteFile: async (fileId: string) => { - const res = await fetch(`${API_BASE}/api/files/delete?user=${encodeURIComponent(DEFAULT_USER)}&file_id=${encodeURIComponent(fileId)}`, { + const res = await fetch(`${API_BASE}/api/files/delete?user=${encodeURIComponent(getCurrentUser())}&file_id=${encodeURIComponent(fileId)}`, { method: 'POST', }); if (!res.ok) { -- cgit v1.2.3