refactor: replace Supabase with self-hosted backend API
This commit is contained in:
parent
e1f2c8d536
commit
8801591132
141
src/services/api.ts
Normal file
141
src/services/api.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import type { User, Event, Note, AIConversation, EventType } from '../types';
|
||||
|
||||
// API Base URL - from environment variable
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
|
||||
|
||||
// Token storage
|
||||
const TOKEN_KEY = 'qia_token';
|
||||
const REFRESH_TOKEN_KEY = 'qia_refresh_token';
|
||||
|
||||
export const api = {
|
||||
// Token management
|
||||
getToken: () => localStorage.getItem(TOKEN_KEY),
|
||||
getRefreshToken: () => localStorage.getItem(REFRESH_TOKEN_KEY),
|
||||
setTokens: (token: string, refreshToken: string) => {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken);
|
||||
},
|
||||
clearTokens: () => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
||||
},
|
||||
|
||||
// Generic request helper
|
||||
async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||
const token = api.getToken();
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
const response = await fetch(`${API_URL}${endpoint}`, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Request failed');
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
// Auth API
|
||||
auth: {
|
||||
register: async (email: string, password: string, nickname?: string) => {
|
||||
return api.request<{ user: User; token: string; refreshToken: string }>(
|
||||
'/auth/register',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password, nickname }),
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
return api.request<{ user: User; token: string; refreshToken: string }>(
|
||||
'/auth/login',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
return api.request('/auth/logout', { method: 'POST' });
|
||||
},
|
||||
|
||||
getCurrentUser: async () => {
|
||||
return api.request<{ user: User }>('/auth/me');
|
||||
},
|
||||
|
||||
refreshToken: async () => {
|
||||
const refreshToken = api.getRefreshToken();
|
||||
if (!refreshToken) throw new Error('No refresh token');
|
||||
|
||||
return api.request<{ token: string }>('/auth/refresh', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
// Events API
|
||||
events: {
|
||||
getAll: async (type?: EventType) => {
|
||||
const query = type ? `?type=${type}` : '';
|
||||
return api.request<Event[]>(`/events${query}`);
|
||||
},
|
||||
|
||||
getById: async (id: string) => {
|
||||
return api.request<Event>(`/events/${id}`);
|
||||
},
|
||||
|
||||
create: async (event: Partial<Event>) => {
|
||||
return api.request<Event>('/events', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(event),
|
||||
});
|
||||
},
|
||||
|
||||
update: async (id: string, updates: Partial<Event>) => {
|
||||
return api.request<Event>(`/events/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(updates),
|
||||
});
|
||||
},
|
||||
|
||||
delete: async (id: string) => {
|
||||
return api.request(`/events/${id}`, { method: 'DELETE' });
|
||||
},
|
||||
},
|
||||
|
||||
// Notes API
|
||||
notes: {
|
||||
get: async () => {
|
||||
return api.request<Note>('/notes');
|
||||
},
|
||||
|
||||
update: async (content: string) => {
|
||||
return api.request<Note>('/notes', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ content }),
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
// AI API
|
||||
ai: {
|
||||
parse: async (message: string) => {
|
||||
return api.request<{ parsed: any; response: string }>('/ai/parse', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ message }),
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { User, Event, Note, AIConversation } from '../types';
|
||||
import { auth } from '../services/supabase';
|
||||
import { api } from '../services/api';
|
||||
|
||||
interface AppState {
|
||||
// Auth state
|
||||
@ -34,7 +34,7 @@ interface AppState {
|
||||
|
||||
export const useAppStore = create<AppState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
(set) => ({
|
||||
// Initial state
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
@ -62,45 +62,61 @@ export const useAppStore = create<AppState>()(
|
||||
|
||||
// Auth actions
|
||||
login: async (email, password) => {
|
||||
const { error } = await auth.signIn(email, password);
|
||||
if (!error) {
|
||||
const { session } = await auth.getSession();
|
||||
if (session?.user) {
|
||||
const user: User = {
|
||||
id: session.user.id,
|
||||
email: session.user.email!,
|
||||
created_at: session.user.created_at,
|
||||
updated_at: session.user.updated_at,
|
||||
};
|
||||
set({ user, isAuthenticated: true });
|
||||
}
|
||||
try {
|
||||
const { user, token, refreshToken } = await api.auth.login(email, password);
|
||||
api.setTokens(token, refreshToken);
|
||||
set({ user, isAuthenticated: true });
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
return { error: error.message || '登录失败' };
|
||||
}
|
||||
return { error };
|
||||
},
|
||||
|
||||
register: async (email, password, nickname) => {
|
||||
const { error } = await auth.signUp(email, password, nickname);
|
||||
return { error };
|
||||
try {
|
||||
const { user, token, refreshToken } = await api.auth.register(email, password, nickname);
|
||||
api.setTokens(token, refreshToken);
|
||||
set({ user, isAuthenticated: true });
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
return { error: error.message || '注册失败' };
|
||||
}
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
await auth.signOut();
|
||||
try {
|
||||
await api.auth.logout();
|
||||
} catch {
|
||||
// Ignore logout error
|
||||
}
|
||||
api.clearTokens();
|
||||
set({ user: null, isAuthenticated: false, events: [], notes: null });
|
||||
},
|
||||
|
||||
checkAuth: async () => {
|
||||
set({ isLoading: true });
|
||||
const { session } = await auth.getSession();
|
||||
if (session?.user) {
|
||||
const user: User = {
|
||||
id: session.user.id,
|
||||
email: session.user.email!,
|
||||
created_at: session.user.created_at,
|
||||
updated_at: session.user.updated_at,
|
||||
};
|
||||
set({ user, isAuthenticated: true, isLoading: false });
|
||||
} else {
|
||||
const token = api.getToken();
|
||||
|
||||
if (!token) {
|
||||
set({ isAuthenticated: false, isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { user } = await api.auth.getCurrentUser();
|
||||
set({ user, isAuthenticated: true, isLoading: false });
|
||||
} catch {
|
||||
// Token invalid, try refresh
|
||||
try {
|
||||
const { token: newToken } = await api.auth.refreshToken();
|
||||
const { user } = await api.auth.getCurrentUser();
|
||||
const refreshToken = api.getRefreshToken()!;
|
||||
api.setTokens(newToken, refreshToken);
|
||||
set({ user, isAuthenticated: true, isLoading: false });
|
||||
} catch {
|
||||
api.clearTokens();
|
||||
set({ isAuthenticated: false, isLoading: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user