1ST
This commit is contained in:
316
frontend/src/lib/api.ts
Normal file
316
frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import axios, { AxiosResponse, AxiosError } from 'axios';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import {
|
||||
Todo,
|
||||
TodoCreate,
|
||||
TodoUpdate,
|
||||
TodoFilter,
|
||||
TodosResponse,
|
||||
User,
|
||||
UserPreferences,
|
||||
LdapUser,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
FireEmailRequest,
|
||||
FireEmailQuota,
|
||||
ImportJob,
|
||||
} from '@/types';
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000';
|
||||
|
||||
// Create axios instance
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Request interceptor to add auth token
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
// Response interceptor for error handling
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
const originalRequest = error.config as any;
|
||||
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
try {
|
||||
const refreshToken = localStorage.getItem('refresh_token');
|
||||
if (refreshToken) {
|
||||
const response = await api.post('/api/auth/refresh', {}, {
|
||||
headers: { Authorization: `Bearer ${refreshToken}` },
|
||||
});
|
||||
|
||||
const { access_token } = response.data;
|
||||
localStorage.setItem('access_token', access_token);
|
||||
|
||||
// Retry original request (mark it to skip toast on failure)
|
||||
originalRequest._isRetry = true;
|
||||
return api(originalRequest);
|
||||
}
|
||||
} catch (refreshError) {
|
||||
// Refresh failed, redirect to login
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
return Promise.reject(refreshError);
|
||||
}
|
||||
}
|
||||
|
||||
// Show error toast (skip for retry requests to avoid duplicates)
|
||||
if (!originalRequest._isRetry) {
|
||||
const errorData = (error as any).response?.data;
|
||||
const status = (error as any).response?.status;
|
||||
let errorMessage = 'An error occurred';
|
||||
|
||||
if (errorData?.message) {
|
||||
errorMessage = errorData.message;
|
||||
} else if (errorData?.error) {
|
||||
errorMessage = errorData.error;
|
||||
} else if ((error as any).message) {
|
||||
errorMessage = (error as any).message;
|
||||
}
|
||||
|
||||
// Special handling for database connection errors
|
||||
if (status === 503) {
|
||||
toast.error(errorMessage, {
|
||||
duration: 5000,
|
||||
style: {
|
||||
backgroundColor: '#fef3c7',
|
||||
color: '#92400e',
|
||||
},
|
||||
});
|
||||
} else if (status === 504) {
|
||||
toast.error(errorMessage, {
|
||||
duration: 4000,
|
||||
style: {
|
||||
backgroundColor: '#fee2e2',
|
||||
color: '#991b1b',
|
||||
},
|
||||
});
|
||||
} else if (status !== 401) {
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Auth API
|
||||
export const authApi = {
|
||||
login: async (credentials: LoginRequest): Promise<LoginResponse> => {
|
||||
const response = await api.post('/api/auth/login', credentials);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
logout: async (): Promise<void> => {
|
||||
await api.post('/api/auth/logout');
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('user');
|
||||
},
|
||||
|
||||
getCurrentUser: async (): Promise<User> => {
|
||||
const response = await api.get('/api/auth/me');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
validateToken: async (): Promise<boolean> => {
|
||||
try {
|
||||
await api.get('/api/auth/validate');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Todos API
|
||||
export const todosApi = {
|
||||
getTodos: async (filter: TodoFilter & { page?: number; per_page?: number }): Promise<TodosResponse> => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
Object.entries(filter).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
params.append(key, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const response = await api.get(`/api/todos?${params.toString()}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTodo: async (id: string): Promise<Todo> => {
|
||||
const response = await api.get(`/api/todos/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
createTodo: async (todo: TodoCreate): Promise<Todo> => {
|
||||
const response = await api.post('/api/todos', todo);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateTodo: async (id: string, updates: Partial<TodoUpdate>): Promise<Todo> => {
|
||||
const response = await api.patch(`/api/todos/${id}`, updates);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
deleteTodo: async (id: string): Promise<void> => {
|
||||
await api.delete(`/api/todos/${id}`);
|
||||
},
|
||||
|
||||
batchUpdateTodos: async (todoIds: string[], updates: Partial<TodoUpdate>): Promise<{ updated: number; errors: any[] }> => {
|
||||
const response = await api.patch('/api/todos/batch', {
|
||||
todo_ids: todoIds,
|
||||
updates,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
fireEmail: async (request: FireEmailRequest): Promise<void> => {
|
||||
await api.post('/api/notifications/fire-email', {
|
||||
todo_id: request.todo_id,
|
||||
message: request.note,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Users API
|
||||
export const usersApi = {
|
||||
searchUsers: async (query: string): Promise<LdapUser[]> => {
|
||||
const response = await api.get(`/api/users/search?q=${encodeURIComponent(query)}`);
|
||||
return response.data.users;
|
||||
},
|
||||
|
||||
getPreferences: async (): Promise<UserPreferences> => {
|
||||
const response = await api.get('/api/users/preferences');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updatePreferences: async (preferences: Partial<UserPreferences>): Promise<UserPreferences> => {
|
||||
const response = await api.patch('/api/users/preferences', preferences);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getFireEmailQuota: async (): Promise<FireEmailQuota> => {
|
||||
const response = await api.get('/api/users/fire-email-quota');
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Import API
|
||||
export const importApi = {
|
||||
downloadTemplate: async (): Promise<Blob> => {
|
||||
const response = await api.get('/api/imports/template', {
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
uploadFile: async (file: File): Promise<ImportJob> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await api.post('/api/imports', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getImportJob: async (jobId: string): Promise<ImportJob> => {
|
||||
const response = await api.get(`/api/imports/${jobId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
downloadErrors: async (jobId: string): Promise<Blob> => {
|
||||
const response = await api.get(`/api/imports/${jobId}/errors`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// Admin API (if needed)
|
||||
export const adminApi = {
|
||||
getStats: async (days: number = 30): Promise<any> => {
|
||||
const response = await api.get(`/api/admin/stats?days=${days}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getAuditLogs: async (params: any): Promise<any> => {
|
||||
const queryParams = new URLSearchParams(params).toString();
|
||||
const response = await api.get(`/api/admin/audit-logs?${queryParams}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getMailLogs: async (params: any): Promise<any> => {
|
||||
const queryParams = new URLSearchParams(params).toString();
|
||||
const response = await api.get(`/api/admin/mail-logs?${queryParams}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Notifications API
|
||||
export const notificationsApi = {
|
||||
getSettings: async (): Promise<any> => {
|
||||
const response = await api.get('/api/notifications/settings');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateSettings: async (settings: any): Promise<any> => {
|
||||
const response = await api.patch('/api/notifications/settings', settings);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
sendTestEmail: async (recipientEmail?: string): Promise<void> => {
|
||||
await api.post('/api/notifications/test', recipientEmail ? { recipient_email: recipientEmail } : {});
|
||||
},
|
||||
|
||||
sendDigest: async (type: 'weekly' | 'monthly' = 'weekly'): Promise<void> => {
|
||||
await api.post('/api/notifications/digest', { type });
|
||||
},
|
||||
|
||||
markNotificationRead: async (notificationId: string): Promise<void> => {
|
||||
await api.post('/api/notifications/mark-read', { notification_id: notificationId });
|
||||
},
|
||||
|
||||
markAllNotificationsRead: async (): Promise<void> => {
|
||||
await api.post('/api/notifications/mark-all-read');
|
||||
},
|
||||
|
||||
getNotifications: async (): Promise<any> => {
|
||||
const response = await api.get('/api/notifications/');
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
// Health API
|
||||
export const healthApi = {
|
||||
check: async (): Promise<any> => {
|
||||
const response = await api.get('/api/health/healthz');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
readiness: async (): Promise<any> => {
|
||||
const response = await api.get('/api/health/readiness');
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
Reference in New Issue
Block a user