Files
bookhoard/frontend/src/lib/api.ts
T
john-okeefe 55c42f1f99 feat: Add comprehensive backend validation, toast notifications, and Tokyo Night theme
- Backend: Add server-side validation with go-playground/validator/v10
- Frontend: Add toast notifications for API errors with @zerodevx/svelte-toast
- UI: Complete Tokyo Night theme redesign with modern animations
- Docs: Update COMPLETE_DOCUMENTATION.md and README.md with all enhancements
- Validation: Email format, password strength, and input sanitization
- UX: Real-time error feedback, loading states, and responsive design
2026-01-21 20:01:18 -05:00

170 lines
4.8 KiB
TypeScript

import { showError } from './toast';
const API_BASE = import.meta.env.DEV ? 'http://localhost:8080/api' : '/api';
// Helper function to handle API responses and show error toasts
async function handleResponse<T>(response: Response, errorMessage: string): Promise<T> {
if (!response.ok) {
let message = errorMessage;
try {
const errorData = await response.json();
if (errorData.error) {
message = errorData.error;
}
} catch (e) {
// If we can't parse JSON, use the default message
}
showError(message);
throw new Error(message);
}
return response.json();
}
export interface User {
id: string;
email: string;
username: string;
}
export interface AuthResponse {
token: string;
user: User;
}
export interface Ebook {
id: string;
title: string;
author: string | null;
isbn: string | null;
description: string | null;
file_path: string;
file_size: number | null;
mime_type: string | null;
cover_image_path: string | null;
created_at: string;
updated_at: string;
}
export interface ReadingProgress {
ebook_id: string;
user_id: string;
current_page: number;
total_pages: number | null;
last_read_at: string;
}
// Get stored token
function getToken(): string | null {
if (typeof window !== 'undefined') {
return localStorage.getItem('auth_token');
}
return null;
}
// Create headers with auth
function createHeaders(): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
const token = getToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
// Auth functions
export async function registerUser(email: string, username: string, password: string): Promise<AuthResponse> {
const response = await fetch(`${API_BASE}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, username, password }),
});
return handleResponse<AuthResponse>(response, 'Registration failed');
}
export async function loginUser(login: string, password: string): Promise<AuthResponse> {
const response = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ login, password }),
});
return handleResponse<AuthResponse>(response, 'Login failed');
}
export async function getUserProfile(): Promise<User> {
const response = await fetch(`${API_BASE}/auth/profile`, {
headers: createHeaders(),
});
return handleResponse<User>(response, 'Failed to get profile');
}
export async function fetchEbooks(limit: number = 20, offset: number = 0): Promise<Ebook[]> {
const response = await fetch(`${API_BASE}/ebooks?limit=${limit}&offset=${offset}`, {
headers: createHeaders(),
});
return handleResponse<Ebook[]>(response, 'Failed to fetch ebooks');
}
export async function fetchEbook(id: string): Promise<Ebook> {
const response = await fetch(`${API_BASE}/ebooks/${id}`, {
headers: createHeaders(),
});
return handleResponse<Ebook>(response, 'Failed to fetch ebook');
}
export async function createEbook(data: Partial<Ebook>): Promise<Ebook> {
const response = await fetch(`${API_BASE}/ebooks`, {
method: 'POST',
headers: createHeaders(),
body: JSON.stringify(data),
});
return handleResponse<Ebook>(response, 'Failed to create ebook');
}
export async function updateEbook(id: string, data: Partial<Ebook>): Promise<Ebook> {
const response = await fetch(`${API_BASE}/ebooks/${id}`, {
method: 'PUT',
headers: createHeaders(),
body: JSON.stringify(data),
});
return handleResponse<Ebook>(response, 'Failed to update ebook');
}
export async function deleteEbook(id: string): Promise<void> {
const response = await fetch(`${API_BASE}/ebooks/${id}`, {
method: 'DELETE',
headers: createHeaders(),
});
if (!response.ok) {
let message = 'Failed to delete ebook';
try {
const errorData = await response.json();
if (errorData.error) {
message = errorData.error;
}
} catch (e) {
// If we can't parse JSON, use the default message
}
showError(message);
throw new Error(message);
}
}
export async function fetchReadingProgress(ebookId: string): Promise<ReadingProgress> {
const response = await fetch(`${API_BASE}/ebooks/${ebookId}/progress`, {
headers: createHeaders(),
});
return handleResponse<ReadingProgress>(response, 'Failed to fetch reading progress');
}
export async function updateReadingProgress(ebookId: string, currentPage: number, totalPages?: number): Promise<ReadingProgress> {
const response = await fetch(`${API_BASE}/ebooks/${ebookId}/progress`, {
method: 'PUT',
headers: createHeaders(),
body: JSON.stringify({ current_page: currentPage, total_pages: totalPages }),
});
return handleResponse<ReadingProgress>(response, 'Failed to update reading progress');
}