dressed_for_succes_store/frontend/lib/api.ts
ilya_zahvatkin 41c1385546 for deploy
2025-05-01 18:29:38 +07:00

261 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios from 'axios';
// Получаем URL API из переменных окружения или используем значение по умолчанию
// Для клиентских запросов (из браузера)
export const PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL || '/api';
// Для серверных запросов (SSR)
export const SERVER_API_URL = process.env.NEXT_SERVER_API_URL || 'http://localhost/api';
// Базовый URL для статических ресурсов
export const PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost';
// Определяем, выполняется ли код на сервере или в браузере
export const isServer = typeof window === 'undefined';
// Константа для ключа токена в localStorage
const TOKEN_KEY = 'token';
// Отладочный режим для API
export const apiStatus = {
// Используем переменную окружения для включения/выключения отладки
debugMode: process.env.NEXT_PUBLIC_DEBUG === 'true',
connectionError: false,
lastConnectionError: null as Error | null,
isAuthenticated: false as boolean, // Флаг аутентификации пользователя (не const)
SUCCESS: 'success',
ERROR: 'error'
};
// Для отладки выводим URL API в консоль
console.log('🔍 Режим отладки API включен');
console.error('🌐 Клиентский API URL:', PUBLIC_API_URL);
console.log('🖥️ Серверный API URL:', SERVER_API_URL);
console.log('📁 BASE URL:', PUBLIC_BASE_URL);
console.log('🔄 Среда выполнения:', isServer ? 'Сервер' : 'Браузер');
console.log('📋 ENV переменные:');
console.log(' - NEXT_PUBLIC_API_URL:', process.env.NEXT_PUBLIC_API_URL);
console.log(' - NEXT_SERVER_API_URL:', process.env.NEXT_SERVER_API_URL);
console.log(' - NEXT_PUBLIC_BASE_URL:', process.env.NEXT_PUBLIC_BASE_URL);
console.log(' - NEXT_PUBLIC_DEBUG:', process.env.NEXT_PUBLIC_DEBUG);
// Проверяем, не содержит ли URL порт 8000
if (PUBLIC_API_URL.includes(':8000')) {
console.warn('⚠️ Клиентский API URL содержит порт 8000, что может вызывать проблемы при работе через Nginx');
}
if (apiStatus.debugMode) {
}
// Получение токена из localStorage
const getToken = (): string | null => {
try {
return localStorage.getItem(TOKEN_KEY);
} catch (error) {
console.error('Ошибка при получении токена (api.ts):', error);
return null;
}
};
// Создаем экземпляр клиента Axios с базовыми настройками
// Используем разные URL для клиента и сервера
const instance = axios.create({
baseURL: isServer ? SERVER_API_URL : PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
timeout: 30000, // 30 секунд таймаут для запросов
});
// Логируем, какой URL используется
if (apiStatus.debugMode) {
console.log('🔌 Axios использует baseURL:', instance.defaults.baseURL);
}
// Добавляем перехватчик для исходящих запросов
instance.interceptors.request.use(
(config) => {
// Получаем токен из localStorage (если он есть)
const token = typeof window !== 'undefined' ? getToken() : null;
// Если токен есть, добавляем его в заголовки запроса
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${token}`;
apiStatus.isAuthenticated = true;
} else {
apiStatus.isAuthenticated = false;
}
// Логируем URL запроса в режиме отладки
if (apiStatus.debugMode) {
console.log(`API Request: ${config.method?.toUpperCase()} ${config.url}`);
}
return config;
},
(error) => {
// Обработка ошибок при подготовке запроса
console.error('API Request Error:', error);
return Promise.reject(error);
}
);
// Добавляем перехватчик для входящих ответов
instance.interceptors.response.use(
function(response: any) {
// Обработка успешного ответа
if (apiStatus.debugMode) {
console.log(`API Response ${response.status} for ${response.config.url}`);
}
return response.data;
},
function(error: any) {
// Проверяем, является ли ошибка 401 (неавторизован) для запроса корзины
// и пользователь не аутентифицирован - это ожидаемое поведение
if (error.response && error.response.status === 401) {
// Для запросов к корзине, когда пользователь не аутентифицирован,
// просто возвращаем пустой объект без логирования ошибки
if (!apiStatus.isAuthenticated &&
error.config.url &&
(error.config.url.includes('/cart') || error.config.url.includes('/wishlist'))) {
return Promise.resolve({});
}
// Логируем 401 ошибку, но НЕ удаляем токен, чтобы избежать проблем при перезагрузке
console.log('Получен 401 ответ от API. Возможно, токен истек или недействителен.');
// Больше не перенаправляем на страницу входа автоматически
// это должно делаться на уровне компонентов проверки авторизации
}
// Логируем ошибки в режиме отладки
if (apiStatus.debugMode) {
if (error.response) {
console.error(`API Error ${error.response.status}: ${error.response.statusText}`);
console.error('API Error Response Data:', error.response.data);
} else if (error.request) {
console.error('API No Response Received:', error.request);
} else {
console.error('API Request Setup Error:', error.message);
}
}
return Promise.reject(error);
}
);
// Общий интерфейс для ответа API
export interface ApiResponse<T> {
success: boolean;
data: T;
}
export interface ApiErrorResponse {
success: false;
error: string;
details?: any;
}
// API клиент
const api = {
// GET запрос
get: async <T>(url: string, params = {}): Promise<T> => {
try {
return await instance.get(url, { params }) as unknown as T;
} catch (error) {
if (apiStatus.debugMode) {
console.error(`GET Error for ${url}:`, error);
}
throw error;
}
},
// POST запрос
post: async <T>(url: string, data = {}, config = {}): Promise<T> => {
try {
return await instance.post(url, data, config) as unknown as T;
} catch (error) {
if (apiStatus.debugMode) {
console.error(`POST Error for ${url}:`, error);
}
throw error;
}
},
// PUT запрос
put: async <T>(url: string, data = {}, config = {}): Promise<T> => {
try {
return await instance.put(url, data, config) as unknown as T;
} catch (error) {
if (apiStatus.debugMode) {
console.error(`PUT Error for ${url}:`, error);
}
throw error;
}
},
// DELETE запрос
delete: async <T>(url: string, config = {}): Promise<T> => {
try {
return await instance.delete(url, config) as unknown as T;
} catch (error) {
if (apiStatus.debugMode) {
console.error(`DELETE Error for ${url}:`, error);
}
throw error;
}
},
};
// Функция для выполнения запросов к API с унифицированным обработчиком ошибок
export async function fetchApi<T>(
url: string,
options: RequestInit = {}
): Promise<ApiResponse<T>> {
try {
const token = typeof window !== 'undefined' ? getToken() : null;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'Accept': 'application/json',
...(options.headers as Record<string, string> || {})
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
// Выбираем правильный базовый URL в зависимости от среды выполнения
const baseUrl = isServer ? SERVER_API_URL : PUBLIC_API_URL;
// Логируем запрос в режиме отладки
if (apiStatus.debugMode) {
console.log(`🔄 fetchApi запрос: ${baseUrl}${url}`);
}
const response = await fetch(`${baseUrl}${url}`, {
...options,
headers,
});
const data = await response.json();
if (!response.ok) {
return {
success: false,
data: data as T,
};
}
return {
success: true,
data: data as T,
};
} catch (error) {
return {
success: false,
data: {} as T,
};
}
}
export default api;