import axios, { AxiosInstance, AxiosError } from 'axios'; import config from '../config'; import { logger, logApiCall } from './logger'; import { ApiResponse, JackpotNextResponse, BuyTicketsResponse, TicketStatusResponse, } from '../types'; class ApiClient { private client: AxiosInstance; constructor() { this.client = axios.create({ baseURL: config.api.baseUrl, timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); // Response interceptor for logging this.client.interceptors.response.use( (response) => { logApiCall( response.config.url || '', response.config.method?.toUpperCase() || 'GET', response.status ); return response; }, (error: AxiosError) => { logApiCall( error.config?.url || '', error.config?.method?.toUpperCase() || 'GET', error.response?.status, error.message ); throw error; } ); } /** * Get next jackpot cycle information */ async getNextJackpot(): Promise { try { const response = await this.client.get>( '/jackpot/next' ); return response.data.data; } catch (error) { if (axios.isAxiosError(error)) { const status = error.response?.status; if (status === 503) { // No active lottery or cycle return null; } } logger.error('Failed to get next jackpot', { error }); throw error; } } /** * Buy lottery tickets */ async buyTickets( tickets: number, lightningAddress: string, displayName: string = 'Anon' ): Promise { try { const response = await this.client.post>( '/jackpot/buy', { tickets, lightning_address: lightningAddress, buyer_name: displayName || 'Anon', } ); return response.data.data; } catch (error) { if (axios.isAxiosError(error)) { const errorData = error.response?.data as ApiResponse; if (errorData?.error) { throw new Error(errorData.message || errorData.error); } } logger.error('Failed to buy tickets', { error }); throw error; } } /** * Get ticket purchase status */ async getTicketStatus(purchaseId: string): Promise { try { const response = await this.client.get>( `/tickets/${purchaseId}` ); return response.data.data; } catch (error) { if (axios.isAxiosError(error) && error.response?.status === 404) { return null; } logger.error('Failed to get ticket status', { error, purchaseId }); throw error; } } /** * Get user's ticket purchases by lightning address * Note: This queries tickets by lightning address pattern matching */ async getUserTickets( telegramUserId: number, limit: number = 10, offset: number = 0 ): Promise { // Since the backend doesn't have Telegram-specific endpoints, // we'll need to track purchases locally in state // This is a placeholder for future backend integration return []; } /** * Health check */ async healthCheck(): Promise { try { await this.client.get('/jackpot/next'); return true; } catch (error) { return false; } } /** * Check if system is in maintenance mode */ async checkMaintenanceStatus(): Promise<{ enabled: boolean; pending: boolean; message: string | null }> { try { const response = await this.client.get>( '/status/maintenance' ); return { enabled: response.data.data.maintenance_mode, pending: response.data.data.maintenance_pending, message: response.data.data.message, }; } catch (error) { // If endpoint doesn't exist or fails, assume not in maintenance return { enabled: false, pending: false, message: null }; } } } export const apiClient = new ApiClient(); export default apiClient;