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
This commit is contained in:
@@ -189,3 +189,122 @@ export async function retryPayout(req: Request, res: Response) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /admin/maintenance
|
||||
* Get current maintenance mode status
|
||||
*/
|
||||
export async function getMaintenanceStatus(req: Request, res: Response) {
|
||||
try {
|
||||
const result = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_mode'`
|
||||
);
|
||||
const pendingResult = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_pending'`
|
||||
);
|
||||
const messageResult = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_message'`
|
||||
);
|
||||
|
||||
const enabled = result.rows[0]?.value === 'true';
|
||||
const pending = pendingResult.rows[0]?.value === 'true';
|
||||
const message = messageResult.rows[0]?.value || 'System is under maintenance. Please try again later.';
|
||||
|
||||
return res.json({
|
||||
version: '1.0',
|
||||
data: {
|
||||
maintenance_mode: enabled,
|
||||
maintenance_pending: pending,
|
||||
message: message,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Get maintenance status error:', error);
|
||||
return res.status(500).json({
|
||||
version: '1.0',
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: 'Failed to get maintenance status',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /admin/maintenance
|
||||
* Enable or disable maintenance mode
|
||||
* When enabling, maintenance is set to "pending" and activates after current draw completes
|
||||
*/
|
||||
export async function setMaintenanceMode(req: Request, res: Response) {
|
||||
try {
|
||||
const { enabled, message, immediate } = req.body;
|
||||
|
||||
if (typeof enabled !== 'boolean') {
|
||||
return res.status(400).json({
|
||||
version: '1.0',
|
||||
error: 'INVALID_INPUT',
|
||||
message: 'enabled must be a boolean',
|
||||
});
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (immediate === true) {
|
||||
// Immediate activation (admin override)
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_mode', 'true', datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = 'true', updated_at = datetime('now')`
|
||||
);
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_pending', 'false', datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = datetime('now')`
|
||||
);
|
||||
console.log('Maintenance mode ENABLED IMMEDIATELY');
|
||||
} else {
|
||||
// Set pending - will activate after current draw completes
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_pending', 'true', datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = 'true', updated_at = datetime('now')`
|
||||
);
|
||||
console.log('Maintenance mode PENDING (will activate after current draw)');
|
||||
}
|
||||
} else {
|
||||
// Disable both active and pending maintenance
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_mode', 'false', datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = datetime('now')`
|
||||
);
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_pending', 'false', datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = datetime('now')`
|
||||
);
|
||||
console.log('Maintenance mode DISABLED');
|
||||
}
|
||||
|
||||
// Update message if provided
|
||||
if (message && typeof message === 'string') {
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_message', $1, datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = $1, updated_at = datetime('now')`,
|
||||
[message]
|
||||
);
|
||||
}
|
||||
|
||||
// Get current state
|
||||
const modeResult = await db.query(`SELECT value FROM system_settings WHERE key = 'maintenance_mode'`);
|
||||
const pendingResult = await db.query(`SELECT value FROM system_settings WHERE key = 'maintenance_pending'`);
|
||||
|
||||
return res.json({
|
||||
version: '1.0',
|
||||
data: {
|
||||
maintenance_mode: modeResult.rows[0]?.value === 'true',
|
||||
maintenance_pending: pendingResult.rows[0]?.value === 'true',
|
||||
message: message || 'System is under maintenance. Please try again later.',
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Set maintenance mode error:', error);
|
||||
return res.status(500).json({
|
||||
version: '1.0',
|
||||
error: 'INTERNAL_ERROR',
|
||||
message: 'Failed to set maintenance mode',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,68 @@ import config from '../config';
|
||||
import { JackpotCycle, TicketPurchase, Ticket, Payout } from '../types';
|
||||
import { AuthRequest } from '../middleware/auth';
|
||||
|
||||
/**
|
||||
* GET /status/maintenance
|
||||
* Public endpoint to check maintenance mode
|
||||
*/
|
||||
export async function getPublicMaintenanceStatus(req: Request, res: Response) {
|
||||
try {
|
||||
const result = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_mode'`
|
||||
);
|
||||
const pendingResult = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_pending'`
|
||||
);
|
||||
const messageResult = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_message'`
|
||||
);
|
||||
|
||||
const enabled = result.rows[0]?.value === 'true';
|
||||
const pending = pendingResult.rows[0]?.value === 'true';
|
||||
const message = messageResult.rows[0]?.value || 'System is under maintenance. Please try again later.';
|
||||
|
||||
return res.json({
|
||||
version: '1.0',
|
||||
data: {
|
||||
maintenance_mode: enabled,
|
||||
maintenance_pending: pending,
|
||||
message: enabled ? message : (pending ? 'Maintenance will begin after the current draw completes.' : null),
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
// If table doesn't exist yet, return not in maintenance
|
||||
return res.json({
|
||||
version: '1.0',
|
||||
data: {
|
||||
maintenance_mode: false,
|
||||
maintenance_pending: false,
|
||||
message: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if system is in maintenance mode
|
||||
*/
|
||||
async function isMaintenanceMode(): Promise<{ enabled: boolean; message: string }> {
|
||||
try {
|
||||
const result = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_mode'`
|
||||
);
|
||||
const messageResult = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_message'`
|
||||
);
|
||||
|
||||
return {
|
||||
enabled: result.rows[0]?.value === 'true',
|
||||
message: messageResult.rows[0]?.value || 'System is under maintenance. Please try again later.',
|
||||
};
|
||||
} catch {
|
||||
return { enabled: false, message: '' };
|
||||
}
|
||||
}
|
||||
|
||||
const toIsoString = (value: any): string => {
|
||||
if (!value) {
|
||||
return new Date().toISOString();
|
||||
@@ -140,6 +202,16 @@ export async function getNextJackpot(req: Request, res: Response) {
|
||||
*/
|
||||
export async function buyTickets(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
// Check maintenance mode first
|
||||
const maintenance = await isMaintenanceMode();
|
||||
if (maintenance.enabled) {
|
||||
return res.status(503).json({
|
||||
version: '1.0',
|
||||
error: 'MAINTENANCE_MODE',
|
||||
message: maintenance.message,
|
||||
});
|
||||
}
|
||||
|
||||
const { tickets, lightning_address, nostr_pubkey, name, buyer_name } = req.body;
|
||||
const userId = req.user?.id || null;
|
||||
const authNostrPubkey = req.user?.nostr_pubkey || null;
|
||||
|
||||
@@ -158,6 +158,17 @@ class DatabaseWrapper {
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- Initialize default settings
|
||||
INSERT OR IGNORE INTO system_settings (key, value) VALUES ('maintenance_mode', 'false');
|
||||
INSERT OR IGNORE INTO system_settings (key, value) VALUES ('maintenance_pending', 'false');
|
||||
INSERT OR IGNORE INTO system_settings (key, value) VALUES ('maintenance_message', 'System is under maintenance. Please try again later.');
|
||||
|
||||
-- Create indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_cycles_status_time ON jackpot_cycles(status, scheduled_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_ticketpurchase_paymenthash ON ticket_purchases(lnbits_payment_hash);
|
||||
|
||||
@@ -3,7 +3,9 @@ import {
|
||||
listCycles,
|
||||
runDrawManually,
|
||||
retryPayout,
|
||||
listPayouts
|
||||
listPayouts,
|
||||
getMaintenanceStatus,
|
||||
setMaintenanceMode
|
||||
} from '../controllers/admin';
|
||||
import { verifyAdmin } from '../middleware/auth';
|
||||
|
||||
@@ -126,5 +128,78 @@ router.get('/payouts', listPayouts);
|
||||
*/
|
||||
router.post('/payouts/:id/retry', retryPayout);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin/maintenance:
|
||||
* get:
|
||||
* summary: Get maintenance mode status
|
||||
* tags: [Admin]
|
||||
* security:
|
||||
* - adminKey: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Maintenance status
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* maintenance_mode:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 403:
|
||||
* description: Invalid admin key
|
||||
*/
|
||||
router.get('/maintenance', getMaintenanceStatus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin/maintenance:
|
||||
* post:
|
||||
* summary: Enable or disable maintenance mode
|
||||
* description: When enabling, maintenance is set to "pending" and activates after current draw completes. Use immediate=true to activate immediately.
|
||||
* tags: [Admin]
|
||||
* security:
|
||||
* - adminKey: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - enabled
|
||||
* properties:
|
||||
* enabled:
|
||||
* type: boolean
|
||||
* description: Whether to enable maintenance mode
|
||||
* message:
|
||||
* type: string
|
||||
* description: Custom maintenance message
|
||||
* immediate:
|
||||
* type: boolean
|
||||
* description: If true, activate immediately instead of waiting for draw to complete
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Maintenance mode updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* maintenance_mode:
|
||||
* type: boolean
|
||||
* maintenance_pending:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: Invalid input
|
||||
* 403:
|
||||
* description: Invalid admin key
|
||||
*/
|
||||
router.post('/maintenance', setMaintenanceMode);
|
||||
|
||||
export default router;
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import {
|
||||
getNextJackpot,
|
||||
buyTickets,
|
||||
getTicketStatus,
|
||||
getPastWins
|
||||
getPastWins,
|
||||
getPublicMaintenanceStatus
|
||||
} from '../controllers/public';
|
||||
import { buyRateLimiter, ticketStatusRateLimiter } from '../middleware/rateLimit';
|
||||
import { optionalAuth } from '../middleware/auth';
|
||||
@@ -187,5 +188,32 @@ router.get('/jackpot/past-wins', getPastWins);
|
||||
*/
|
||||
router.get('/tickets/:id', ticketStatusRateLimiter, getTicketStatus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /status/maintenance:
|
||||
* get:
|
||||
* summary: Check if system is in maintenance mode
|
||||
* tags: [Public]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Maintenance status
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* version:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* maintenance_mode:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* nullable: true
|
||||
*/
|
||||
router.get('/status/maintenance', getPublicMaintenanceStatus);
|
||||
|
||||
export default router;
|
||||
|
||||
|
||||
@@ -244,6 +244,8 @@ async function checkAndExecuteDraws(): Promise<void> {
|
||||
await executeDraw(cycle.id);
|
||||
// Cancel unpaid invoices for this cycle after draw
|
||||
await cancelUnpaidPurchases(cycle.id);
|
||||
// Check if maintenance was pending and activate it
|
||||
await activatePendingMaintenance();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -275,6 +277,33 @@ async function cancelUnpaidPurchases(cycleId: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if maintenance is pending and activate it after draw completion
|
||||
*/
|
||||
async function activatePendingMaintenance(): Promise<void> {
|
||||
try {
|
||||
const pendingResult = await db.query(
|
||||
`SELECT value FROM system_settings WHERE key = 'maintenance_pending'`
|
||||
);
|
||||
|
||||
if (pendingResult.rows[0]?.value === 'true') {
|
||||
// Activate maintenance mode
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_mode', 'true', datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = 'true', updated_at = datetime('now')`
|
||||
);
|
||||
// Clear pending flag
|
||||
await db.query(
|
||||
`INSERT INTO system_settings (key, value, updated_at) VALUES ('maintenance_pending', 'false', datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = datetime('now')`
|
||||
);
|
||||
console.log('🔧 MAINTENANCE MODE ACTIVATED (pending maintenance enabled after draw)');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error activating pending maintenance:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cycles to 'sales_open' status when appropriate
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,8 @@ export default function BuyPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [jackpot, setJackpot] = useState<any>(null);
|
||||
const [maintenanceMode, setMaintenanceMode] = useState(false);
|
||||
const [maintenanceMessage, setMaintenanceMessage] = useState<string | null>(null);
|
||||
|
||||
// Form state
|
||||
const [lightningAddress, setLightningAddress] = useState('');
|
||||
@@ -125,6 +127,11 @@ export default function BuyPage() {
|
||||
|
||||
const loadJackpot = async () => {
|
||||
try {
|
||||
// Check maintenance status first
|
||||
const maintenanceStatus = await api.getMaintenanceStatus();
|
||||
setMaintenanceMode(maintenanceStatus.maintenance_mode);
|
||||
setMaintenanceMessage(maintenanceStatus.message);
|
||||
|
||||
const response = await api.getNextJackpot();
|
||||
if (response.data) {
|
||||
setJackpot(response.data);
|
||||
@@ -221,6 +228,30 @@ export default function BuyPage() {
|
||||
const ticketPriceSats = jackpot.lottery.ticket_price_sats;
|
||||
const totalCost = ticketPriceSats * tickets;
|
||||
|
||||
// Show maintenance message if in maintenance mode
|
||||
if (maintenanceMode) {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-1">
|
||||
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold mb-6 sm:mb-8 text-center text-white">
|
||||
{STRINGS.buy.title}
|
||||
</h1>
|
||||
<div className="bg-gradient-to-r from-amber-900/60 via-amber-800/50 to-amber-900/60 border border-amber-500/50 rounded-xl p-6 sm:p-8 text-center">
|
||||
<span className="text-5xl mb-4 block">🔧</span>
|
||||
<h2 className="text-2xl font-bold text-amber-300 mb-3">Maintenance Mode</h2>
|
||||
<p className="text-amber-200/80 text-lg">
|
||||
{maintenanceMessage || 'System is under maintenance. Please try again later.'}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => router.push('/')}
|
||||
className="mt-6 bg-amber-700 hover:bg-amber-600 text-white px-6 py-2 rounded-lg transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-1">
|
||||
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold mb-6 sm:mb-8 text-center text-white">
|
||||
|
||||
@@ -33,6 +33,20 @@ export default function HomePage() {
|
||||
const [isRecentWin, setIsRecentWin] = useState(false);
|
||||
const [awaitingNextCycle, setAwaitingNextCycle] = useState(false);
|
||||
const [pendingWinner, setPendingWinner] = useState<RecentWinner | null>(null);
|
||||
const [maintenanceMode, setMaintenanceMode] = useState(false);
|
||||
const [maintenancePending, setMaintenancePending] = useState(false);
|
||||
const [maintenanceMessage, setMaintenanceMessage] = useState<string | null>(null);
|
||||
|
||||
const loadMaintenanceStatus = useCallback(async () => {
|
||||
try {
|
||||
const status = await api.getMaintenanceStatus();
|
||||
setMaintenanceMode(status.maintenance_mode);
|
||||
setMaintenancePending(status.maintenance_pending);
|
||||
setMaintenanceMessage(status.message);
|
||||
} catch {
|
||||
// Ignore errors, assume not in maintenance
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadJackpot = useCallback(async () => {
|
||||
try {
|
||||
@@ -76,9 +90,10 @@ export default function HomePage() {
|
||||
}, [drawJustCompleted]);
|
||||
|
||||
useEffect(() => {
|
||||
loadMaintenanceStatus();
|
||||
loadJackpot();
|
||||
loadRecentWinner();
|
||||
}, [loadJackpot, loadRecentWinner]);
|
||||
}, [loadMaintenanceStatus, loadJackpot, loadRecentWinner]);
|
||||
|
||||
// Detect when draw time passes and trigger draw animation (only if tickets were sold)
|
||||
useEffect(() => {
|
||||
@@ -245,6 +260,36 @@ export default function HomePage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Maintenance Mode Banner */}
|
||||
{maintenanceMode && (
|
||||
<div className="bg-gradient-to-r from-amber-900/60 via-amber-800/50 to-amber-900/60 border border-amber-500/50 rounded-xl sm:rounded-2xl p-4 sm:p-6 mb-6 sm:mb-8">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<span className="text-3xl">🔧</span>
|
||||
<div className="text-center">
|
||||
<h3 className="text-xl font-bold text-amber-300">Maintenance Mode</h3>
|
||||
<p className="text-amber-200/80 mt-1">
|
||||
{maintenanceMessage || 'System is under maintenance. Please try again later.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pending Maintenance Banner */}
|
||||
{!maintenanceMode && maintenancePending && (
|
||||
<div className="bg-gradient-to-r from-blue-900/40 via-blue-800/30 to-blue-900/40 border border-blue-500/50 rounded-xl sm:rounded-2xl p-4 sm:p-6 mb-6 sm:mb-8">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<span className="text-2xl">⏳</span>
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-bold text-blue-300">Maintenance Scheduled</h3>
|
||||
<p className="text-blue-200/80 mt-1">
|
||||
Maintenance will begin after the current draw completes. Last chance to buy tickets!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="text-center mb-8 sm:mb-12">
|
||||
<h1 className="text-3xl sm:text-4xl md:text-6xl font-bold mb-3 sm:mb-4 text-white">
|
||||
@@ -313,7 +358,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
|
||||
{/* Buy Button - Hide when waiting for next round */}
|
||||
{!isWaitingForNextRound && (
|
||||
{!isWaitingForNextRound && !maintenanceMode && (
|
||||
<div className="flex justify-center">
|
||||
<Link
|
||||
href="/buy"
|
||||
|
||||
@@ -157,6 +157,16 @@ class ApiClient {
|
||||
return this.request(`/jackpot/past-wins?${params.toString()}`);
|
||||
}
|
||||
|
||||
async getMaintenanceStatus(): Promise<{ maintenance_mode: boolean; maintenance_pending: boolean; message: string | null }> {
|
||||
try {
|
||||
const response = await this.request<{ data: { maintenance_mode: boolean; maintenance_pending: boolean; message: string | null } }>('/status/maintenance');
|
||||
return response.data;
|
||||
} catch {
|
||||
// If endpoint doesn't exist or fails, assume not in maintenance
|
||||
return { maintenance_mode: false, maintenance_pending: false, message: null };
|
||||
}
|
||||
}
|
||||
|
||||
// Auth endpoints
|
||||
async nostrAuth(nostrPubkey: string, signedMessage: string, nonce: string) {
|
||||
return this.request('/auth/nostr', {
|
||||
|
||||
@@ -33,6 +33,26 @@ export async function handleBuyCommand(
|
||||
logUserAction(userId, 'Initiated ticket purchase');
|
||||
|
||||
try {
|
||||
// Check maintenance mode first
|
||||
const maintenance = await apiClient.checkMaintenanceStatus();
|
||||
if (maintenance.enabled) {
|
||||
await bot.sendMessage(
|
||||
chatId,
|
||||
messages.errors.maintenance(maintenance.message || 'System is under maintenance.'),
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Warn if maintenance is pending (but still allow purchase)
|
||||
if (maintenance.pending) {
|
||||
await bot.sendMessage(
|
||||
chatId,
|
||||
messages.errors.maintenancePending,
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
}
|
||||
|
||||
const user = await stateManager.getUser(userId);
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -20,6 +20,17 @@ export const messages = {
|
||||
checkStatusFailed: '❌ Failed to check status',
|
||||
noPendingPurchase: '❌ No pending purchase. Please start again with /buyticket',
|
||||
setAddressFirst: '❌ Please set your Lightning Address first.',
|
||||
maintenance: (message: string) => `🔧 *Maintenance Mode*
|
||||
|
||||
${message}
|
||||
|
||||
Please try again later. We'll be back soon! ⚡`,
|
||||
|
||||
maintenancePending: `⏳ *Maintenance Scheduled*
|
||||
|
||||
Maintenance will begin after the current draw completes.
|
||||
|
||||
This is your *last chance* to buy tickets for the current round! 🎟️`,
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -138,6 +138,25 @@ class ApiClient {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user