2025-04-03 12:21:10 +07:00
|
|
|
import axios, { AxiosRequestConfig } from 'axios';
|
|
|
|
|
|
2025-03-19 23:38:42 +07:00
|
|
|
export * from './auth';
|
|
|
|
|
export * from './gacha';
|
|
|
|
|
export * from './users';
|
2025-08-18 12:50:32 +07:00
|
|
|
export * from './mentors';
|
|
|
|
|
export * from './upload';
|
|
|
|
|
|
|
|
|
|
// Common API response wrapper interface
|
|
|
|
|
export interface ApiResponse<T> {
|
|
|
|
|
data: T;
|
|
|
|
|
version: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TOKEN_KEY = 'token';
|
|
|
|
|
|
|
|
|
|
// Helper functions for session management (avoiding circular dependency)
|
|
|
|
|
const getSessionTokenFromCookies = () => {
|
|
|
|
|
if (typeof document === 'undefined') return null;
|
|
|
|
|
|
|
|
|
|
const cookies = document.cookie.split(';');
|
|
|
|
|
const tokenCookie = cookies.find(cookie => cookie.trim().startsWith(`${TOKEN_KEY}=`));
|
|
|
|
|
|
|
|
|
|
if (!tokenCookie) return null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const tokenValue = tokenCookie.split('=')[1];
|
|
|
|
|
return JSON.parse(decodeURIComponent(tokenValue));
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const setSessionTokenToCookies = (tokenData: { token: { access_token: string; refresh_token: string } }) => {
|
|
|
|
|
if (typeof document === 'undefined') return;
|
|
|
|
|
|
|
|
|
|
const expires = new Date();
|
|
|
|
|
expires.setDate(expires.getDate() + 7);
|
|
|
|
|
|
|
|
|
|
document.cookie = `${TOKEN_KEY}=${encodeURIComponent(JSON.stringify(tokenData))}; expires=${expires.toUTCString()}; path=/; secure; samesite=strict`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeSessionTokenFromCookies = () => {
|
|
|
|
|
if (typeof document === 'undefined') return;
|
|
|
|
|
|
|
|
|
|
document.cookie = `${TOKEN_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
|
|
|
};
|
2025-04-03 12:21:10 +07:00
|
|
|
|
|
|
|
|
const config: AxiosRequestConfig = {
|
|
|
|
|
baseURL: import.meta.env.VITE_API_URL,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const api = axios.create(config);
|
2025-08-18 12:50:32 +07:00
|
|
|
|
|
|
|
|
// Add request interceptor to include authentication token
|
|
|
|
|
api.interceptors.request.use(
|
|
|
|
|
(config) => {
|
|
|
|
|
const sessionData = getSessionTokenFromCookies();
|
|
|
|
|
const token = sessionData?.token?.access_token;
|
|
|
|
|
|
|
|
|
|
if (token) {
|
|
|
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return config;
|
|
|
|
|
},
|
|
|
|
|
(error) => {
|
|
|
|
|
return Promise.reject(new Error(error.message || 'Request failed'));
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Add response interceptor to handle token refresh
|
|
|
|
|
api.interceptors.response.use(
|
|
|
|
|
(response) => response,
|
|
|
|
|
async (error) => {
|
|
|
|
|
const originalRequest = error.config;
|
|
|
|
|
|
|
|
|
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
|
|
|
originalRequest._retry = true;
|
|
|
|
|
return handleTokenRefresh(originalRequest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If backend sends a message, use it
|
|
|
|
|
const backendMsg = error?.response?.data?.message;
|
|
|
|
|
if (backendMsg && typeof backendMsg === 'string') {
|
|
|
|
|
return Promise.reject(new Error(backendMsg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Promise.reject(new Error(error.message || 'Request failed'));
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Helper function to handle token refresh
|
|
|
|
|
async function handleTokenRefresh(originalRequest: AxiosRequestConfig) {
|
|
|
|
|
const sessionData = getSessionTokenFromCookies();
|
|
|
|
|
const refreshToken = sessionData?.token?.refresh_token;
|
|
|
|
|
|
|
|
|
|
if (!refreshToken) {
|
|
|
|
|
clearSessionAndRedirect();
|
|
|
|
|
throw new Error('No refresh token available');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await refreshAccessToken(refreshToken);
|
|
|
|
|
|
|
|
|
|
if (response.data?.access_token) {
|
|
|
|
|
// Update session in cookies
|
|
|
|
|
setSessionTokenToCookies({
|
|
|
|
|
token: {
|
|
|
|
|
access_token: response.data.access_token,
|
|
|
|
|
refresh_token: response.data.refresh_token || refreshToken,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Retry original request with new token
|
|
|
|
|
originalRequest.headers ??= {};
|
|
|
|
|
originalRequest.headers.Authorization = `Bearer ${response.data.access_token}`;
|
|
|
|
|
return api(originalRequest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new Error('Invalid refresh response');
|
|
|
|
|
} catch (refreshError) {
|
|
|
|
|
console.error('Token refresh failed:', refreshError);
|
|
|
|
|
clearSessionAndRedirect();
|
|
|
|
|
throw new Error('Token refresh failed');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to refresh access token
|
|
|
|
|
async function refreshAccessToken(refreshToken: string) {
|
|
|
|
|
return axios.post(`${import.meta.env.VITE_API_URL}/auth/refresh`, {
|
|
|
|
|
refresh_token: refreshToken,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to clear session and redirect
|
|
|
|
|
function clearSessionAndRedirect() {
|
|
|
|
|
removeSessionTokenFromCookies();
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
window.location.href = '/auth/login';
|
|
|
|
|
}
|
|
|
|
|
}
|