2026-01-20 13:58:13 -05:00
|
|
|
import axios from 'axios';
|
|
|
|
|
|
|
|
|
|
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
|
|
|
|
|
|
|
|
|
|
const api = axios.create({
|
|
|
|
|
baseURL: API_BASE_URL,
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Add auth token to requests
|
|
|
|
|
api.interceptors.request.use((config) => {
|
|
|
|
|
const token = localStorage.getItem('token');
|
|
|
|
|
if (token) {
|
|
|
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
|
|
|
}
|
|
|
|
|
return config;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Handle auth errors
|
|
|
|
|
api.interceptors.response.use(
|
|
|
|
|
(response) => response,
|
|
|
|
|
(error) => {
|
|
|
|
|
if (error.response?.status === 401) {
|
|
|
|
|
localStorage.removeItem('token');
|
|
|
|
|
localStorage.removeItem('user');
|
|
|
|
|
window.location.href = '/login';
|
|
|
|
|
}
|
|
|
|
|
return Promise.reject(error);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Auth API
|
|
|
|
|
export const authApi = {
|
|
|
|
|
register: (email: string, password: string) =>
|
|
|
|
|
api.post('/auth/register', { email, password }),
|
|
|
|
|
|
|
|
|
|
login: (email: string, password: string) =>
|
|
|
|
|
api.post('/auth/login', { email, password }),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Products API
|
2026-01-20 20:54:12 -05:00
|
|
|
export type StockStatus = 'in_stock' | 'out_of_stock' | 'unknown';
|
|
|
|
|
|
2026-01-20 19:32:25 -05:00
|
|
|
export interface SparklinePoint {
|
|
|
|
|
price: number;
|
|
|
|
|
recorded_at: string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 13:58:13 -05:00
|
|
|
export interface Product {
|
|
|
|
|
id: number;
|
|
|
|
|
user_id: number;
|
|
|
|
|
url: string;
|
|
|
|
|
name: string | null;
|
|
|
|
|
image_url: string | null;
|
|
|
|
|
refresh_interval: number;
|
|
|
|
|
last_checked: string | null;
|
2026-01-20 20:54:12 -05:00
|
|
|
stock_status: StockStatus;
|
2026-01-20 21:15:04 -05:00
|
|
|
price_drop_threshold: number | null;
|
|
|
|
|
notify_back_in_stock: boolean;
|
2026-01-20 13:58:13 -05:00
|
|
|
created_at: string;
|
|
|
|
|
current_price: number | null;
|
|
|
|
|
currency: string | null;
|
2026-01-20 19:32:25 -05:00
|
|
|
sparkline?: SparklinePoint[];
|
|
|
|
|
price_change_7d?: number | null;
|
2026-01-20 13:58:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ProductWithStats extends Product {
|
|
|
|
|
stats: {
|
|
|
|
|
min_price: number;
|
|
|
|
|
max_price: number;
|
|
|
|
|
avg_price: number;
|
|
|
|
|
price_count: number;
|
|
|
|
|
} | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PriceHistory {
|
|
|
|
|
id: number;
|
|
|
|
|
product_id: number;
|
|
|
|
|
price: number;
|
|
|
|
|
currency: string;
|
|
|
|
|
recorded_at: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const productsApi = {
|
|
|
|
|
getAll: () => api.get<Product[]>('/products'),
|
|
|
|
|
|
|
|
|
|
getById: (id: number) => api.get<ProductWithStats>(`/products/${id}`),
|
|
|
|
|
|
|
|
|
|
create: (url: string, refreshInterval?: number) =>
|
|
|
|
|
api.post<Product>('/products', { url, refresh_interval: refreshInterval }),
|
|
|
|
|
|
2026-01-20 21:15:04 -05:00
|
|
|
update: (id: number, data: {
|
|
|
|
|
name?: string;
|
|
|
|
|
refresh_interval?: number;
|
|
|
|
|
price_drop_threshold?: number | null;
|
|
|
|
|
notify_back_in_stock?: boolean;
|
|
|
|
|
}) => api.put<Product>(`/products/${id}`, data),
|
2026-01-20 13:58:13 -05:00
|
|
|
|
|
|
|
|
delete: (id: number) => api.delete(`/products/${id}`),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Prices API
|
|
|
|
|
export const pricesApi = {
|
|
|
|
|
getHistory: (productId: number, days?: number) =>
|
|
|
|
|
api.get<{ product: Product; prices: PriceHistory[] }>(
|
|
|
|
|
`/products/${productId}/prices`,
|
|
|
|
|
{ params: days ? { days } : undefined }
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
refresh: (productId: number) =>
|
|
|
|
|
api.post<{ message: string; price: PriceHistory }>(
|
|
|
|
|
`/products/${productId}/refresh`
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-20 21:15:04 -05:00
|
|
|
// Settings API
|
|
|
|
|
export interface NotificationSettings {
|
|
|
|
|
telegram_configured: boolean;
|
|
|
|
|
telegram_chat_id: string | null;
|
|
|
|
|
discord_configured: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const settingsApi = {
|
|
|
|
|
getNotifications: () =>
|
|
|
|
|
api.get<NotificationSettings>('/settings/notifications'),
|
|
|
|
|
|
|
|
|
|
updateNotifications: (data: {
|
|
|
|
|
telegram_bot_token?: string | null;
|
|
|
|
|
telegram_chat_id?: string | null;
|
|
|
|
|
discord_webhook_url?: string | null;
|
|
|
|
|
}) => api.put<NotificationSettings & { message: string }>('/settings/notifications', data),
|
|
|
|
|
|
|
|
|
|
testTelegram: () =>
|
|
|
|
|
api.post<{ message: string }>('/settings/notifications/test/telegram'),
|
|
|
|
|
|
|
|
|
|
testDiscord: () =>
|
|
|
|
|
api.post<{ message: string }>('/settings/notifications/test/discord'),
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-20 13:58:13 -05:00
|
|
|
export default api;
|