- Replace `any` types with proper interfaces (QuestDetailResponse, etc.) - Remove unused imports (createUser, findUserByTelegramId, updatePointContent) - Use `unknown` in catch blocks with instanceof narrowing - Type Express handlers with Request/Response/NextFunction - Add argsIgnorePattern for underscore-prefixed params in ESLint config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
3.6 KiB
TypeScript
118 lines
3.6 KiB
TypeScript
import 'dotenv/config'
|
|
import express, { type Request, type Response, type NextFunction } 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: (req: Request, res: Response) => Promise<void>) {
|
|
return async (req: Request, res: Response, next: NextFunction) => {
|
|
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: Request, res: Response, next: NextFunction) => {
|
|
if (req.path.startsWith('/api/')) {
|
|
return next()
|
|
}
|
|
res.sendFile(path.join(distPath, 'index.html'))
|
|
})
|
|
}
|
|
|
|
// Error handler
|
|
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
|
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()
|