Files
LightningLotto/telegram_bot/src/services/api.ts
Michilis 404fdf2610 Maintenance mode activates after current draw completes
- When admin enables maintenance, it's set to 'pending' state
- Maintenance activates automatically after the current draw completes
- Admin can use immediate=true to force immediate activation
- Frontend shows 'Maintenance Scheduled' banner when pending
- Telegram bot warns users but still allows purchases when pending
- Both mode and pending status tracked in system_settings table
2025-12-09 00:46:55 +00:00

166 lines
4.3 KiB
TypeScript

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<JackpotNextResponse | null> {
try {
const response = await this.client.get<ApiResponse<JackpotNextResponse>>(
'/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<BuyTicketsResponse> {
try {
const response = await this.client.post<ApiResponse<BuyTicketsResponse>>(
'/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<unknown>;
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<TicketStatusResponse | null> {
try {
const response = await this.client.get<ApiResponse<TicketStatusResponse>>(
`/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<TicketStatusResponse[]> {
// 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<boolean> {
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<ApiResponse<{ maintenance_mode: boolean; maintenance_pending: boolean; message: string | null }>>(
'/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;