From ae8e72119e4943162f02210ec5057f57f1b35f47 Mon Sep 17 00:00:00 2001 From: kappa Date: Thu, 12 Feb 2026 09:38:52 +0900 Subject: [PATCH] Restrict troubleshoot/billing/asset to customers only Non-customers (no assets and no wallet balance) are redirected to onboarding agent. Customer check queries servers, domains, DDoS/VPN services, and wallet balance. Admins bypass the check. On DB error, defaults to allowing access to prevent false lockouts. Co-Authored-By: Claude Opus 4.6 --- src/routes/handlers/message-handler.ts | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/routes/handlers/message-handler.ts b/src/routes/handlers/message-handler.ts index dab6251..dd81f2b 100644 --- a/src/routes/handlers/message-handler.ts +++ b/src/routes/handlers/message-handler.ts @@ -96,6 +96,24 @@ export async function handleMessage( const intent = await classifyIntent(env, text); logger.info('의도 분류 결과', { intent, telegramUserId }); + // 고객 전용 에이전트(troubleshoot, billing, asset)는 자산 또는 잔액이 있는 사용자만 이용 가능 + // admin은 제한 없음 + const customerOnlyIntents = ['troubleshoot', 'billing', 'asset']; + if (intent && customerOnlyIntents.includes(intent) && user.role !== 'admin') { + const isCustomer = await checkIsCustomer(env.DB, telegramUserId); + if (!isCustomer) { + logger.info('비고객 → 온보딩 전환', { intent, telegramUserId }); + const response = await onboardingAgent.processConsultation(env.DB, telegramUserId, text, env, meta); + const { cleanText, sessionEnded } = cleanSessionMarkers(response); + await storeConversation(env.DB, user.id, text, cleanText, requestId); + await sendMessage(env.BOT_TOKEN, chatId, cleanText); + if (sessionEnded) { + await promptFeedback(env, chatId, lang, 'onboarding'); + } + return; + } + } + const agentMap: Record = { onboarding: { agent: onboardingAgent, type: 'onboarding' }, troubleshoot: { agent: troubleshootAgent, type: 'troubleshoot' }, @@ -173,6 +191,27 @@ async function getOrCreateUser( } } +async function checkIsCustomer(db: D1Database, telegramUserId: string): Promise { + try { + const result = await db.prepare( + `SELECT + (SELECT COUNT(*) FROM servers WHERE user_id = u.id) + + (SELECT COUNT(*) FROM domains WHERE user_id = u.id) + + (SELECT COUNT(*) FROM services_ddos WHERE user_id = u.id) + + (SELECT COUNT(*) FROM services_vpn WHERE user_id = u.id) AS asset_count, + (SELECT COALESCE(balance, 0) FROM wallets WHERE user_id = u.id) AS balance + FROM users u WHERE u.telegram_id = ?` + ).bind(telegramUserId).first<{ asset_count: number; balance: number }>(); + + if (!result) return false; + return result.asset_count > 0 || (result.balance ?? 0) > 0; + } catch (error) { + logger.error('고객 확인 실패', error as Error, { telegramUserId }); + // 에러 시 고객으로 간주 (서비스 차단 방지) + return true; + } +} + const VALID_INTENTS = ['troubleshoot', 'onboarding', 'billing', 'asset', 'general'] as const; async function classifyIntent(env: Env, text: string): Promise {