Telegram Bot + Mini App for city walking quests. - React 19 + TypeScript + Vite 6 frontend - Express 5 + PostgreSQL backend - grammY Telegram bot with DeepSeek AI - GitLab CI/CD: lint, build, deploy to production Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
import type { ApiRequest, ApiResponse } from '../_lib/types.js'
|
|
import { cors, withTelegramAuth } from '../_lib/auth.js'
|
|
import { findUserByTelegramId, pool } from '../_lib/db.js'
|
|
|
|
async function handler(req: ApiRequest, res: ApiResponse) {
|
|
cors(req, res)
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
return res.status(200).end()
|
|
}
|
|
|
|
if (req.method !== 'GET') {
|
|
return res.status(405).json({ error: 'Method not allowed' })
|
|
}
|
|
|
|
const telegramUser = req.telegramUser
|
|
if (!telegramUser) {
|
|
return res.status(401).json({ error: 'Not authenticated' })
|
|
}
|
|
|
|
const user = await findUserByTelegramId(telegramUser.id)
|
|
if (!user) {
|
|
return res.status(404).json({ error: 'User not found' })
|
|
}
|
|
|
|
const questId = req.query.id as string | undefined
|
|
|
|
if (questId) {
|
|
// Get specific quest with days and points
|
|
const { rows: quests } = await pool.query(
|
|
`SELECT q.*, c.name as city_name, c.country as city_country
|
|
FROM quests q
|
|
JOIN cities c ON q.city_id = c.id
|
|
WHERE q.id = $1 AND q.user_id = $2`,
|
|
[questId, user.id]
|
|
)
|
|
|
|
if (quests.length === 0) {
|
|
return res.status(404).json({ error: 'Quest not found' })
|
|
}
|
|
|
|
const { rows: days } = await pool.query(
|
|
`SELECT d.*,
|
|
COALESCE(
|
|
json_agg(
|
|
jsonb_build_object(
|
|
'id', p.id, 'title', p.title, 'teaser_text', p.teaser_text,
|
|
'content_text', p.content_text, 'status', p.status,
|
|
'order_in_day', p.order_in_day, 'location_lat', p.location_lat,
|
|
'location_lon', p.location_lon
|
|
) ORDER BY p.order_in_day
|
|
) FILTER (WHERE p.id IS NOT NULL),
|
|
'[]'::json
|
|
) as points
|
|
FROM quest_days d
|
|
LEFT JOIN quest_points p ON d.id = p.day_id
|
|
WHERE d.quest_id = $1
|
|
GROUP BY d.id
|
|
ORDER BY d.day_number`,
|
|
[questId]
|
|
)
|
|
|
|
return res.json({ quest: quests[0], days })
|
|
}
|
|
|
|
// List all completed quests
|
|
const { rows: quests } = await pool.query(
|
|
`SELECT q.*, c.name as city_name, c.country as city_country
|
|
FROM quests q
|
|
JOIN cities c ON q.city_id = c.id
|
|
WHERE q.user_id = $1 AND q.status = 'completed'
|
|
ORDER BY q.completed_at DESC`,
|
|
[user.id]
|
|
)
|
|
|
|
return res.json({ quests })
|
|
}
|
|
|
|
export default withTelegramAuth(handler)
|