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 { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import type { User, Event, Note, AIConversation } from '../types';
|
import type { User, Event, Note, AIConversation } from '../types';
|
||||||
import { auth } from '../services/supabase';
|
import { api } from '../services/api';
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
// Auth state
|
// Auth state
|
||||||
@ -34,7 +34,7 @@ interface AppState {
|
|||||||
|
|
||||||
export const useAppStore = create<AppState>()(
|
export const useAppStore = create<AppState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set) => ({
|
||||||
// Initial state
|
// Initial state
|
||||||
user: null,
|
user: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
@ -62,45 +62,61 @@ export const useAppStore = create<AppState>()(
|
|||||||
|
|
||||||
// Auth actions
|
// Auth actions
|
||||||
login: async (email, password) => {
|
login: async (email, password) => {
|
||||||
const { error } = await auth.signIn(email, password);
|
try {
|
||||||
if (!error) {
|
const { user, token, refreshToken } = await api.auth.login(email, password);
|
||||||
const { session } = await auth.getSession();
|
api.setTokens(token, refreshToken);
|
||||||
if (session?.user) {
|
set({ user, isAuthenticated: true });
|
||||||
const user: User = {
|
return { error: null };
|
||||||
id: session.user.id,
|
} catch (error: any) {
|
||||||
email: session.user.email!,
|
return { error: error.message || '登录失败' };
|
||||||
created_at: session.user.created_at,
|
|
||||||
updated_at: session.user.updated_at,
|
|
||||||
};
|
|
||||||
set({ user, isAuthenticated: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return { error };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
register: async (email, password, nickname) => {
|
register: async (email, password, nickname) => {
|
||||||
const { error } = await auth.signUp(email, password, nickname);
|
try {
|
||||||
return { error };
|
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 () => {
|
logout: async () => {
|
||||||
await auth.signOut();
|
try {
|
||||||
|
await api.auth.logout();
|
||||||
|
} catch {
|
||||||
|
// Ignore logout error
|
||||||
|
}
|
||||||
|
api.clearTokens();
|
||||||
set({ user: null, isAuthenticated: false, events: [], notes: null });
|
set({ user: null, isAuthenticated: false, events: [], notes: null });
|
||||||
},
|
},
|
||||||
|
|
||||||
checkAuth: async () => {
|
checkAuth: async () => {
|
||||||
set({ isLoading: true });
|
set({ isLoading: true });
|
||||||
const { session } = await auth.getSession();
|
const token = api.getToken();
|
||||||
if (session?.user) {
|
|
||||||
const user: User = {
|
if (!token) {
|
||||||
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 {
|
|
||||||
set({ isAuthenticated: false, isLoading: false });
|
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