dramaling-vocab-learning/frontend/lib/services/auth.ts

159 lines
4.1 KiB
TypeScript

// Auth service for handling authentication API calls
export interface LoginRequest {
email: string;
password: string;
}
export interface RegisterRequest {
username: string;
email: string;
password: string;
}
export interface User {
id: string;
username: string;
email: string;
displayName: string | null;
avatarUrl: string | null;
subscriptionType: string;
}
export interface AuthResponse {
success: boolean;
data?: {
token: string;
user: User;
};
error?: string;
}
const API_BASE_URL = 'http://localhost:5008';
class AuthService {
private async makeRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${API_BASE_URL}/api/auth${endpoint}`;
console.log('Making request to:', url)
console.log('Request body:', options.body)
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Network error' }));
console.log('API Error Response:', errorData)
// 處理驗證錯誤 (400 Bad Request)
if (response.status === 400 && errorData.errors) {
const validationErrors = Object.entries(errorData.errors)
.map(([field, messages]: [string, any]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`)
.join('\n');
throw new Error(validationErrors);
}
throw new Error(errorData.error || errorData.title || `HTTP ${response.status}`);
}
return response.json();
}
async login(data: LoginRequest): Promise<AuthResponse> {
try {
const response = await this.makeRequest<AuthResponse>('/login', {
method: 'POST',
body: JSON.stringify(data),
});
if (response.success && response.data?.token) {
// Store token in localStorage
localStorage.setItem('auth_token', response.data.token);
localStorage.setItem('user_data', JSON.stringify(response.data.user));
}
return response;
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Login failed',
};
}
}
async register(data: RegisterRequest): Promise<AuthResponse> {
try {
console.log('AuthService.register called with:', {
username: data.username,
email: data.email,
password: data.password ? '[hidden]' : 'empty'
})
const response = await this.makeRequest<AuthResponse>('/register', {
method: 'POST',
body: JSON.stringify(data),
});
if (response.success && response.data?.token) {
// Store token in localStorage
localStorage.setItem('auth_token', response.data.token);
localStorage.setItem('user_data', JSON.stringify(response.data.user));
}
return response;
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Registration failed',
};
}
}
async logout(): Promise<void> {
localStorage.removeItem('auth_token');
localStorage.removeItem('user_data');
}
getToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem('auth_token');
}
getUser(): User | null {
if (typeof window === 'undefined') return null;
const userData = localStorage.getItem('user_data');
return userData ? JSON.parse(userData) : null;
}
isAuthenticated(): boolean {
return this.getToken() !== null;
}
async checkAuthStatus(): Promise<boolean> {
const token = this.getToken();
if (!token) return false;
try {
await this.makeRequest('/status', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
return true;
} catch {
// Token is invalid, clear it
this.logout();
return false;
}
}
}
export const authService = new AuthService();