From c61e6ad9cd5efa2d1e48540d591b23d4a60367e6 Mon Sep 17 00:00:00 2001 From: laruevin Date: Wed, 11 Feb 2026 12:27:42 +0700 Subject: [PATCH] fix: resolve all 32 ESLint errors across codebase - 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 --- bot/bot.ts | 2 +- bot/conversations/onboarding.ts | 6 +++--- bot/services/event-processor.ts | 7 +++---- bot/services/quest.service.ts | 4 ++-- eslint.config.js | 4 ++++ scripts/migrate.ts | 4 ++-- server/index.ts | 10 +++++----- src/pages/HistoryPage.tsx | 12 ++++++------ src/store/questStore.ts | 9 +++++---- src/types/index.ts | 12 ++++++++++++ src/utils/api.ts | 6 +++--- 11 files changed, 46 insertions(+), 30 deletions(-) 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}`) }