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 <noreply@anthropic.com>
This commit is contained in:
parent
9960f0cc78
commit
c61e6ad9cd
@ -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<string, unknown>[][] } | undefined {
|
||||
const appUrl = process.env.APP_URL || 'https://guidly.example.com'
|
||||
if (appUrl.startsWith('https://')) {
|
||||
return { inline_keyboard: [[ { text: label, web_app: { url: appUrl } } ]] }
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Conversation } from '@grammyjs/conversations'
|
||||
import type { BotContext } from '../types.js'
|
||||
|
||||
type BotConversation = Conversation<BotContext, BotContext>
|
||||
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
|
||||
|
||||
@ -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<BotContext>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function processEvent(bot: Bot<BotContext>, event: any) {
|
||||
async function processEvent(bot: Bot<BotContext>, event: Record<string, unknown>) {
|
||||
// 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<BotContext>, 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,
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -23,6 +23,10 @@ export default tseslint.config(
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_' },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<void>) {
|
||||
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' })
|
||||
})
|
||||
|
||||
@ -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<QuestHistoryItem[]>([])
|
||||
const [detail, setDetail] = useState<any>(null)
|
||||
const [detail, setDetail] = useState<QuestDetailResponse | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(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}
|
||||
</p>
|
||||
|
||||
{detail.days.map((day: any) => (
|
||||
{detail.days.map((day: QuestDetailDay) => (
|
||||
<div key={day.id} className={styles.daySection}>
|
||||
<h3 className={styles.dayTitle}>
|
||||
{lang === 'ru' ? `День ${day.day_number}` : `Day ${day.day_number}`}
|
||||
{day.theme && ` — ${day.theme}`}
|
||||
</h3>
|
||||
{day.points.map((point: any) => (
|
||||
{day.points.map((point: QuestPoint) => (
|
||||
<div key={point.id} className={styles.pointCard}>
|
||||
<div className={styles.pointStatus}>
|
||||
{point.status === 'completed' ? 'V' : point.status === 'skipped' ? '-' : 'o'}
|
||||
|
||||
@ -27,11 +27,12 @@ export const useQuestStore = create<QuestStore>((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<QuestStore>((set, get) => ({
|
||||
}
|
||||
|
||||
return response.nextAction
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to send event:', error)
|
||||
return 'wait'
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<QuestDetailResponse>(`/quest/history/${questId}`)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user