You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
3.8 KiB
151 lines
3.8 KiB
import axios from 'axios'; |
|
|
|
const api = axios.create({ |
|
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8080/api', |
|
withCredentials: true, |
|
headers: { 'Content-Type': 'application/json' } |
|
}); |
|
|
|
let isRefreshing = false; |
|
let failedQueue: any[] = []; |
|
|
|
const processQueue = (error: any, token: string | null = null) => { |
|
failedQueue.forEach(prom => { |
|
if (error) { |
|
prom.reject(error); |
|
} else { |
|
prom.resolve(token); |
|
} |
|
}); |
|
failedQueue = []; |
|
}; |
|
|
|
// Request interceptor: attach access 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: handle token refresh |
|
api.interceptors.response.use( |
|
response => response.data, |
|
async error => { |
|
const originalRequest = error.config; |
|
|
|
// If 401 and not already retrying |
|
if (error.response?.status === 401 && !originalRequest._retry) { |
|
if (isRefreshing) { |
|
// Queue this request |
|
return new Promise((resolve, reject) => { |
|
failedQueue.push({ resolve, reject }); |
|
}).then(token => { |
|
originalRequest.headers.Authorization = `Bearer ${token}`; |
|
return api(originalRequest); |
|
}); |
|
} |
|
|
|
originalRequest._retry = true; |
|
isRefreshing = true; |
|
|
|
try { |
|
// Try to refresh token |
|
const response = await axios.post( |
|
`${api.defaults.baseURL}/auth/refresh`, |
|
{}, |
|
{ withCredentials: true } |
|
); |
|
|
|
const { accessToken } = response.data.data; |
|
localStorage.setItem('access_token', accessToken); |
|
|
|
originalRequest.headers.Authorization = `Bearer ${accessToken}`; |
|
processQueue(null, accessToken); |
|
|
|
return api(originalRequest); |
|
} catch (refreshError) { |
|
processQueue(refreshError, null); |
|
localStorage.removeItem('access_token'); |
|
window.location.href = '/login'; |
|
return Promise.reject(refreshError); |
|
} finally { |
|
isRefreshing = false; |
|
} |
|
} |
|
|
|
return Promise.reject(error.response?.data || error); |
|
} |
|
); |
|
|
|
// Auth API |
|
export const authApi = { |
|
login: (username: string, password: string) => |
|
api.post('/auth/login', { username, password }), |
|
|
|
logout: () => api.post('/auth/logout'), |
|
|
|
getCurrentUser: () => api.get('/auth/me'), |
|
|
|
refresh: () => api.post('/auth/refresh') |
|
}; |
|
|
|
// Channels API |
|
export const channelsApi = { |
|
getAll: () => api.get('/channels'), |
|
|
|
add: (channelInput: string) => |
|
api.post('/channels', { channelInput }), |
|
|
|
remove: (channelId: string) => |
|
api.delete(`/channels/${channelId}`), |
|
|
|
refresh: (channelId: string) => |
|
api.put(`/channels/${channelId}/refresh`) |
|
}; |
|
|
|
// Videos API |
|
export const videosApi = { |
|
getAll: (params?: any) => api.get('/videos', { params }), |
|
|
|
search: (query: string, params?: any) => |
|
api.get('/videos/search', { params: { q: query, ...params } }), |
|
|
|
refresh: (channelIds?: string[]) => |
|
api.post('/videos/refresh', { channelIds }) |
|
}; |
|
|
|
// Settings API |
|
export const settingsApi = { |
|
getTimeLimit: () => api.get('/settings/time-limit'), |
|
|
|
setTimeLimit: (dailyLimit: number) => |
|
api.put('/settings/time-limit', { dailyLimit }) |
|
}; |
|
|
|
// Word Groups API |
|
export const wordGroupsApi = { |
|
getAll: () => api.get('/word-groups'), |
|
|
|
create: (name: string) => |
|
api.post('/word-groups', { name }), |
|
|
|
update: (id: number, name: string) => |
|
api.put(`/word-groups/${id}`, { name }), |
|
|
|
delete: (id: number) => |
|
api.delete(`/word-groups/${id}`), |
|
|
|
addWord: (groupId: number, word: string) => |
|
api.post(`/word-groups/${groupId}/words`, { word }), |
|
|
|
deleteWord: (wordId: number) => |
|
api.delete(`/word-groups/words/${wordId}`) |
|
}; |
|
|
|
|
|
|
|
|