diff --git a/bot/bot.ts b/bot/bot.ts
index ffd3bfe..97ebeb4 100644
--- a/bot/bot.ts
+++ b/bot/bot.ts
@@ -8,7 +8,7 @@ import { t, getLang } from './i18n/index.js'
// Helper: build reply_markup for opening the Mini App
// Telegram requires HTTPS for both web_app and url buttons.
// In dev (HTTP), we skip the button entirely and append the link to the message text.
-function questMarkup(label: string): { inline_keyboard: any[][] } | undefined {
+function questMarkup(label: string): { inline_keyboard: Record[][] } | undefined {
const appUrl = process.env.APP_URL || 'https://guidly.example.com'
if (appUrl.startsWith('https://')) {
return { inline_keyboard: [[ { text: label, web_app: { url: appUrl } } ]] }
diff --git a/bot/conversations/onboarding.ts b/bot/conversations/onboarding.ts
index f25e6c6..b8d5f88 100644
--- a/bot/conversations/onboarding.ts
+++ b/bot/conversations/onboarding.ts
@@ -2,7 +2,7 @@ import type { Conversation } from '@grammyjs/conversations'
import type { BotContext } from '../types.js'
type BotConversation = Conversation
-import { getActiveCities, createUser, findUserByTelegramId } from '../../api/_lib/db.js'
+import { getActiveCities, findUserByTelegramId } from '../../api/_lib/db.js'
import { t, getLang } from '../i18n/index.js'
import { createQuestFromOnboarding } from '../services/quest.service.js'
@@ -25,7 +25,7 @@ export async function onboardingConversation(conversation: BotConversation, ctx:
// Step 1: City selection
const cities = await conversation.external(() => getActiveCities())
- const cityButtons = cities.map((city: any) => ([
+ const cityButtons = cities.map((city: { id: string; name: string }) => ([
{ text: city.name, callback_data: `city_${city.id}` }
]))
@@ -35,7 +35,7 @@ export async function onboardingConversation(conversation: BotConversation, ctx:
const cityResponse = await conversation.waitForCallbackQuery(/^city_/)
const cityId = cityResponse.callbackQuery!.data!.replace('city_', '')
- const cityName = cities.find((c: any) => c.id === cityId)?.name || 'Unknown'
+ const cityName = cities.find((c: { id: string }) => c.id === cityId)?.name || 'Unknown'
await cityResponse.answerCallbackQuery()
// Step 2: Number of days
diff --git a/bot/services/event-processor.ts b/bot/services/event-processor.ts
index e0e5d03..5e70fa3 100644
--- a/bot/services/event-processor.ts
+++ b/bot/services/event-processor.ts
@@ -2,9 +2,8 @@ import type { Bot } from 'grammy'
import type { BotContext } from '../types.js'
import {
getUnprocessedEvents, markEventProcessed,
- getPointById, findUserByTelegramId,
+ getPointById,
getActiveQuestForUser, getActiveDayForQuest,
- updatePointContent,
} from '../../api/_lib/db.js'
import { aiService } from './ai.service.js'
import { generateNextPoint, generatePointContent } from './quest.service.js'
@@ -47,7 +46,7 @@ async function processEvents(bot: Bot) {
}
}
-async function processEvent(bot: Bot, event: any) {
+async function processEvent(bot: Bot, event: Record) {
// Look up the user to get their telegram_id for sending messages
const { pool } = await import('../../api/_lib/db.js')
const { rows: users } = await pool.query('SELECT * FROM users WHERE id = $1', [event.user_id])
@@ -97,7 +96,7 @@ async function processEvent(bot: Bot, event: any) {
const day = await getActiveDayForQuest(quest.id)
if (!day) break
- const result = await generateNextPoint(quest.id, {
+ await generateNextPoint(quest.id, {
city: quest.city_name,
dayNumber: day.day_number,
totalDays: quest.number_of_days,
diff --git a/bot/services/quest.service.ts b/bot/services/quest.service.ts
index c3a872a..269ebfb 100644
--- a/bot/services/quest.service.ts
+++ b/bot/services/quest.service.ts
@@ -104,8 +104,8 @@ export async function generateNextPoint(questId: string, questContext: {
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 previousPoints = existingPoints.map((p: { title: string }) => p.title)
+ const maxOrder = existingPoints.reduce((max: number, p: { order_in_day?: number }) => Math.max(max, p.order_in_day || 0), 0)
const nextOrder = maxOrder + 1
const point = await aiService.generatePoint({
diff --git a/eslint.config.js b/eslint.config.js
index 03b1d8a..0777366 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -23,6 +23,10 @@ export default tseslint.config(
'warn',
{ allowConstantExport: true },
],
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ { argsIgnorePattern: '^_' },
+ ],
},
},
)
diff --git a/scripts/migrate.ts b/scripts/migrate.ts
index af089d5..84a14cc 100644
--- a/scripts/migrate.ts
+++ b/scripts/migrate.ts
@@ -50,8 +50,8 @@ async function migrate() {
await pool.query(sql)
await pool.query('INSERT INTO _migrations (name) VALUES ($1)', [file])
console.log(` [ok] ${file}`)
- } catch (error: any) {
- console.error(` [fail] ${file}:`, error.message)
+ } catch (error: unknown) {
+ console.error(` [fail] ${file}:`, error instanceof Error ? error.message : error)
process.exit(1)
}
}
diff --git a/server/index.ts b/server/index.ts
index 1c131a0..e406652 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -1,5 +1,5 @@
import 'dotenv/config'
-import express from 'express'
+import express, { type Request, type Response, type NextFunction } from 'express'
import cookieParser from 'cookie-parser'
import path from 'path'
import { fileURLToPath } from 'url'
@@ -15,8 +15,8 @@ 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) => {
+function wrapHandler(handler: (req: Request, res: Response) => Promise) {
+ return async (req: Request, res: Response, next: NextFunction) => {
const mergedQuery = { ...req.query, ...req.params }
Object.defineProperty(req, 'query', {
value: mergedQuery,
@@ -88,7 +88,7 @@ async function setupRoutes() {
app.use(express.static(distPath))
// SPA fallback
- app.use((req: any, res: any, next: any) => {
+ app.use((req: Request, res: Response, next: NextFunction) => {
if (req.path.startsWith('/api/')) {
return next()
}
@@ -97,7 +97,7 @@ async function setupRoutes() {
}
// Error handler
-app.use((err: any, _req: any, res: any, _next: any) => {
+app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
console.error('Server error:', err)
res.status(500).json({ error: 'Internal server error' })
})
diff --git a/src/pages/HistoryPage.tsx b/src/pages/HistoryPage.tsx
index fa63761..9111f3a 100644
--- a/src/pages/HistoryPage.tsx
+++ b/src/pages/HistoryPage.tsx
@@ -4,13 +4,13 @@ import styles from './HistoryPage.module.css'
import { fetchQuestHistory, fetchQuestDetail } from '@/utils/api'
import { Loader, ErrorState } from '@/components/ui'
import { getLanguage } from '@/utils/telegram'
-import type { QuestHistoryItem } from '@/types'
+import type { QuestHistoryItem, QuestDetailResponse, QuestDetailDay, QuestPoint } from '@/types'
export function HistoryPage() {
const { questId } = useParams()
const lang = getLanguage()
const [quests, setQuests] = useState([])
- const [detail, setDetail] = useState(null)
+ const [detail, setDetail] = useState(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
@@ -25,8 +25,8 @@ export function HistoryPage() {
const data = await fetchQuestHistory()
setQuests(data.quests)
}
- } catch (err: any) {
- setError(err.message)
+ } catch (err: unknown) {
+ setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setIsLoading(false)
}
@@ -50,13 +50,13 @@ export function HistoryPage() {
{detail.quest.city_name}, {detail.quest.city_country}
- {detail.days.map((day: any) => (
+ {detail.days.map((day: QuestDetailDay) => (
{lang === 'ru' ? `День ${day.day_number}` : `Day ${day.day_number}`}
{day.theme && ` — ${day.theme}`}
- {day.points.map((point: any) => (
+ {day.points.map((point: QuestPoint) => (
{point.status === 'completed' ? 'V' : point.status === 'skipped' ? '-' : 'o'}
diff --git a/src/store/questStore.ts b/src/store/questStore.ts
index c0140e6..59637bc 100644
--- a/src/store/questStore.ts
+++ b/src/store/questStore.ts
@@ -27,11 +27,12 @@ export const useQuestStore = create((set, get) => ({
const state = await fetchCurrentQuest()
const showContent = state.point?.arrived_at !== null && state.point?.content_text !== null
set({ state, isLoading: false, showContent })
- } catch (error: any) {
- if (error.message?.includes('NO_ACTIVE_QUEST') || error.message?.includes('User not found')) {
+ } catch (error: unknown) {
+ const message = error instanceof Error ? error.message : 'Unknown error'
+ if (message.includes('NO_ACTIVE_QUEST') || message.includes('User not found')) {
set({ state: null, isLoading: false, error: null })
} else {
- set({ isLoading: false, error: error.message })
+ set({ isLoading: false, error: message })
}
}
},
@@ -72,7 +73,7 @@ export const useQuestStore = create((set, get) => ({
}
return response.nextAction
- } catch (error: any) {
+ } catch (error: unknown) {
console.error('Failed to send event:', error)
return 'wait'
}
diff --git a/src/types/index.ts b/src/types/index.ts
index b7db98d..d25650e 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -49,6 +49,18 @@ export interface EventResponse {
nextAction: 'wait' | 'show_content' | 'next_point' | 'day_complete' | 'quest_complete' | 'close_app'
}
+export interface QuestDetailDay {
+ id: string
+ day_number: number
+ theme: string | null
+ points: QuestPoint[]
+}
+
+export interface QuestDetailResponse {
+ quest: QuestInfo & { city_country: string }
+ days: QuestDetailDay[]
+}
+
export interface QuestHistoryItem {
id: string
title: string | null
diff --git a/src/utils/api.ts b/src/utils/api.ts
index 971f778..13b4b5e 100644
--- a/src/utils/api.ts
+++ b/src/utils/api.ts
@@ -1,5 +1,5 @@
import { getTelegramInitData } from './telegram'
-import type { QuestState, EventResponse } from '../types'
+import type { QuestState, EventResponse, QuestHistoryItem, QuestDetailResponse } from '../types'
const API_BASE = '/api'
@@ -40,9 +40,9 @@ export async function sendQuestEvent(data: {
}
export async function fetchQuestHistory() {
- return apiFetch<{ quests: any[] }>('/quest/history')
+ return apiFetch<{ quests: QuestHistoryItem[] }>('/quest/history')
}
export async function fetchQuestDetail(questId: string) {
- return apiFetch<{ quest: any; days: any[] }>(`/quest/history/${questId}`)
+ return apiFetch(`/quest/history/${questId}`)
}