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 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-02-12 09:38:52 +09:00
parent cde2ddc713
commit ae8e72119e

View File

@@ -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<string, { agent: RegisterableAgent; type: string }> = {
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<boolean> {
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<string | null> {