import 'dotenv/config' import express from 'express' import cookieParser from 'cookie-parser' import path from 'path' import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const app = express() const PORT = process.env.PORT || 3000 // Middleware app.use(express.json({ limit: '10mb' })) app.use(express.urlencoded({ extended: true })) app.use(cookieParser()) // Wrap handler to catch errors and merge params function wrapHandler(handler: Function) { return async (req: any, res: any, next: any) => { const mergedQuery = { ...req.query, ...req.params } Object.defineProperty(req, 'query', { value: mergedQuery, writable: true, configurable: true, }) try { await handler(req, res) } catch (error) { next(error) } } } async function setupRoutes() { // Quest API (Mini App) const questCurrent = (await import('../api/quest/current.js')).default const questEvents = (await import('../api/quest/events.js')).default const questHistory = (await import('../api/quest/history.js')).default // Internal API (Bot) const botInternal = (await import('../api/bot/internal.js')).default // Quest routes (Mini App) app.all('/api/quest/current', wrapHandler(questCurrent)) app.all('/api/quest/events', wrapHandler(questEvents)) app.all('/api/quest/history', wrapHandler(questHistory)) app.all('/api/quest/history/:id', wrapHandler(questHistory)) // Internal bot routes app.all('/api/bot/internal', wrapHandler(botInternal)) // In production: start bot in webhook mode inside this server process const isProduction = process.env.NODE_ENV === 'production' && process.env.BOT_WEBHOOK_URL if (isProduction) { const { webhookCallback } = await import('grammy') const { createBot } = await import('../bot/bot.js') const { startEventProcessor } = await import('../bot/services/event-processor.js') const token = process.env.TELEGRAM_BOT_TOKEN if (!token) { console.error('TELEGRAM_BOT_TOKEN is not set!') process.exit(1) } const bot = createBot(token) await bot.init() // Register grammY webhook handler app.post('/api/telegram/webhook', webhookCallback(bot, 'express')) // Set webhook with Telegram await bot.api.setWebhook(process.env.BOT_WEBHOOK_URL!, { secret_token: process.env.BOT_WEBHOOK_SECRET, }) // Start event processing loop startEventProcessor(bot) console.log('Bot started in webhook mode') } else { // In development: register a placeholder webhook route const telegramWebhook = (await import('../api/telegram/webhook.js')).default app.all('/api/telegram/webhook', wrapHandler(telegramWebhook)) } // Serve static files from dist/ in production const distPath = path.join(__dirname, '..', '..', 'dist') app.use(express.static(distPath)) // SPA fallback app.use((req: any, res: any, next: any) => { if (req.path.startsWith('/api/')) { return next() } res.sendFile(path.join(distPath, 'index.html')) }) } // Error handler app.use((err: any, _req: any, res: any, _next: any) => { console.error('Server error:', err) res.status(500).json({ error: 'Internal server error' }) }) async function start() { try { await setupRoutes() app.listen(PORT, () => { console.log(`Guidly server running on http://localhost:${PORT}`) }) } catch (error) { console.error('Failed to start server:', error) process.exit(1) } } start()