guidly/bot/services/quest.service.ts
laruevin d5ed7fdcf9 Initial commit: Guidly project with CI/CD pipeline
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>
2026-02-11 11:42:42 +07:00

151 lines
3.8 KiB
TypeScript

import {
createQuest, createQuestDay, createQuestPoint,
updateQuestStatus, updateDayStatus,
getActiveDayForQuest, getPointsForDay,
} from '../../api/_lib/db.js'
import { aiService } from './ai.service.js'
interface OnboardingData {
userId: string
cityId: string
cityName: string
days: number
companions: string
pace: string
userComment?: string
lang: string
}
export async function createQuestFromOnboarding(data: OnboardingData) {
try {
// Generate quest plan via AI
const plan = await aiService.generateQuestPlan({
city: data.cityName,
days: data.days,
companions: data.companions,
pace: data.pace,
userComment: data.userComment,
lang: data.lang,
})
// Create quest in DB
const quest = await createQuest({
city_id: data.cityId,
user_id: data.userId,
title: plan.title,
description: plan.description,
number_of_days: data.days,
pace: data.pace,
companions: data.companions,
user_comment: data.userComment,
})
// Create day skeletons
const days = []
for (let i = 0; i < data.days; i++) {
const day = await createQuestDay({
quest_id: quest.id,
day_number: i + 1,
theme: plan.dayThemes[i] || undefined,
})
days.push(day)
}
// Start the quest and first day
await updateQuestStatus(quest.id, 'in_progress')
await updateDayStatus(days[0].id, 'in_progress')
// Generate first point
const firstPoint = await aiService.generatePoint({
city: data.cityName,
dayNumber: 1,
totalDays: data.days,
dayTheme: plan.dayThemes[0] || '',
pace: data.pace,
companions: data.companions,
userComment: data.userComment,
previousPoints: [],
lang: data.lang,
})
await createQuestPoint({
day_id: days[0].id,
title: firstPoint.title,
location_lat: firstPoint.locationLat,
location_lon: firstPoint.locationLon,
teaser_text: firstPoint.teaserText,
order_in_day: 1,
status: 'active',
})
return {
questId: quest.id,
title: plan.title,
description: plan.description,
}
} catch (error) {
console.error('Failed to create quest from onboarding:', error)
return null
}
}
export async function generateNextPoint(questId: string, questContext: {
city: string
dayNumber: number
totalDays: number
dayTheme: string
pace: string
companions: string
userComment?: string
lang: string
}) {
try {
const day = await getActiveDayForQuest(questId)
if (!day) return null
const existingPoints = await getPointsForDay(day.id)
const previousPoints = existingPoints.map((p: any) => p.title)
const maxOrder = existingPoints.reduce((max: number, p: any) => Math.max(max, p.order_in_day || 0), 0)
const nextOrder = maxOrder + 1
const point = await aiService.generatePoint({
...questContext,
previousPoints,
})
const created = await createQuestPoint({
day_id: day.id,
title: point.title,
location_lat: point.locationLat,
location_lon: point.locationLon,
teaser_text: point.teaserText,
order_in_day: nextOrder,
status: 'active',
})
return { point: created, isLastOfDay: point.isLastPointOfDay }
} catch (error) {
console.error('Failed to generate next point:', error)
return null
}
}
export async function generatePointContent(pointId: string, context: {
city: string
pointTitle: string
dayNumber: number
companions: string
pace: string
lang: string
}) {
try {
const content = await aiService.generatePointContent(context)
const { updatePointContent } = await import('../../api/_lib/db.js')
await updatePointContent(pointId, content.contentText)
return content
} catch (error) {
console.error('Failed to generate point content:', error)
return null
}
}