- 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
166 lines
4.3 KiB
TypeScript
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;
|
|
|
|
|