164 lines
4.9 KiB
TypeScript
164 lines
4.9 KiB
TypeScript
// Flashcards API service
|
|
|
|
export interface Flashcard {
|
|
id: string;
|
|
word: string;
|
|
translation: string;
|
|
definition: string;
|
|
partOfSpeech: string;
|
|
pronunciation: string;
|
|
example: string;
|
|
exampleTranslation?: string;
|
|
masteryLevel: number;
|
|
timesReviewed: number;
|
|
isFavorite: boolean;
|
|
nextReviewDate: string;
|
|
difficultyLevel: string;
|
|
createdAt: string;
|
|
updatedAt?: string; // 設為可選,因為模擬資料可能沒有
|
|
// 移除 cardSet 屬性
|
|
}
|
|
|
|
export interface CreateFlashcardRequest {
|
|
word: string;
|
|
translation: string;
|
|
definition: string;
|
|
pronunciation: string;
|
|
partOfSpeech: string;
|
|
example: string;
|
|
exampleTranslation?: string;
|
|
difficultyLevel?: string; // A1, A2, B1, B2, C1, C2
|
|
}
|
|
|
|
export interface ApiResponse<T> {
|
|
success: boolean;
|
|
data?: T;
|
|
error?: string;
|
|
message?: string;
|
|
}
|
|
|
|
class FlashcardsService {
|
|
private readonly baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'}/api`;
|
|
|
|
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers,
|
|
},
|
|
...options,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ error: 'Network error' }));
|
|
throw new Error(errorData.error || errorData.details || `HTTP ${response.status}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
// 詞卡查詢方法 (支援進階篩選、排序和分頁)
|
|
async getFlashcards(
|
|
search?: string,
|
|
favoritesOnly: boolean = false,
|
|
cefrLevel?: string,
|
|
partOfSpeech?: string,
|
|
masteryLevel?: string,
|
|
sortBy?: string,
|
|
sortOrder?: 'asc' | 'desc',
|
|
page?: number,
|
|
limit?: number
|
|
): Promise<ApiResponse<{ flashcards: Flashcard[], count: number }>> {
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (search) params.append('search', search);
|
|
if (favoritesOnly) params.append('favoritesOnly', 'true');
|
|
if (cefrLevel) params.append('cefrLevel', cefrLevel);
|
|
if (partOfSpeech) params.append('partOfSpeech', partOfSpeech);
|
|
if (masteryLevel) params.append('masteryLevel', masteryLevel);
|
|
|
|
// 排序和分頁參數
|
|
if (sortBy) params.append('sortBy', sortBy);
|
|
if (sortOrder) params.append('sortOrder', sortOrder);
|
|
if (page) params.append('page', page.toString());
|
|
if (limit) params.append('limit', limit.toString());
|
|
|
|
const queryString = params.toString();
|
|
const endpoint = `/flashcards${queryString ? `?${queryString}` : ''}`;
|
|
|
|
return await this.makeRequest<ApiResponse<{ flashcards: Flashcard[], count: number }>>(endpoint);
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to fetch flashcards',
|
|
};
|
|
}
|
|
}
|
|
|
|
async createFlashcard(data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>> {
|
|
try {
|
|
return await this.makeRequest<ApiResponse<Flashcard>>('/flashcards', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to create flashcard',
|
|
};
|
|
}
|
|
}
|
|
|
|
async deleteFlashcard(id: string): Promise<ApiResponse<void>> {
|
|
try {
|
|
return await this.makeRequest<ApiResponse<void>>(`/flashcards/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to delete flashcard',
|
|
};
|
|
}
|
|
}
|
|
|
|
async getFlashcard(id: string): Promise<ApiResponse<Flashcard>> {
|
|
try {
|
|
return await this.makeRequest<ApiResponse<Flashcard>>(`/flashcards/${id}`);
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to get flashcard',
|
|
};
|
|
}
|
|
}
|
|
|
|
async updateFlashcard(id: string, data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>> {
|
|
try {
|
|
return await this.makeRequest<ApiResponse<Flashcard>>(`/flashcards/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data),
|
|
});
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to update flashcard',
|
|
};
|
|
}
|
|
}
|
|
|
|
async toggleFavorite(id: string): Promise<ApiResponse<void>> {
|
|
try {
|
|
return await this.makeRequest<ApiResponse<void>>(`/flashcards/${id}/favorite`, {
|
|
method: 'POST',
|
|
});
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to toggle favorite',
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export const flashcardsService = new FlashcardsService(); |