feat: improve server management and refund display

Server Management:
- Fix /server command API auth (query param instead of header)
- Show server specs (vCPU/RAM/Bandwidth) in /server list
- Prevent AI from refusing server deletion based on expiration date
- Add explicit instructions in tool description and system prompt

Refund Display:
- Show before/after balance in server deletion refund message
- Format: 환불 전 잔액 → 환불 금액 → 환불 후 잔액

Other Changes:
- Add stopped status migration for server orders
- Clean up callback handler (remove deprecated code)
- Update constants and pattern utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-30 05:30:59 +09:00
parent 18e7d3ca6e
commit 2b1bc6a371
17 changed files with 1237 additions and 364 deletions

View File

@@ -15,40 +15,66 @@ export async function handleCommand(
switch (command) {
case '/start':
return `👋 안녕하세요! AI 어시스턴트입니다.
return `👋 <b>AnvilHosting 고객센터</b>입니다!
대화를 나눌수록 당신을 더 잘 이해합니다 💡
<b>제공 서비스:</b>
• 🌐 도메인 등록/관리
• 🖥️ 클라우드 서버 (서울/도쿄/오사카/싱가폴)
• 🛡️ DDoS 방어
• 🔐 PhantomX VPN (Xray 기반 차세대 보안)
<b>명령어:</b>
/profile - 내 프로필 보기
/help - 도움말
/deposit - 예치금 잔액
/domain - 내 도메인 목록
/server - 내 서버 목록
/security - DDoS 방어 현황
/phantomx - PhantomX VPN
💡 중요한 정보는 "기억해줘"로 저장하세요!`;
무엇을 도와드릴까요?`;
case '/help':
return `📖 <b>도움말</b>
/profile - 내 프로필 보기
<b>명령어:</b>
/deposit - 예치금 잔액
/domain - 내 도메인 목록
/server - 내 서버 목록
/security - DDoS 방어 서비스
/phantomx - PhantomX VPN 서비스
<b>기억 기능:</b>
• "OOO 기억해줘" - 정보 저장
• "내 기억 보여줘" - 저장 목록
• "OOO 잊어줘" - 삭제
<b>자연어로 요청:</b>
• "도메인 등록" - 도메인 검색/등록
• "서버 추천" - 맞춤 서버 추천
대화할수록 당신을 더 잘 이해합니다 💡`;
궁금한 점은 편하게 물어보세요!`;
case '/deposit': {
const deposit = await env.DB
.prepare('SELECT balance FROM user_deposits WHERE user_id = ?')
.bind(userId)
.first<{ balance: number }>();
const balance = deposit?.balance ?? 0;
return `💰 <b>예치금 잔액</b>
현재 잔액: <b>${balance.toLocaleString()}원</b>
<b>입금 계좌:</b>
하나은행 427-910018-27104
예금주: (주)아이언클래드
입금 후 "홍길동 10000원 입금" 형식으로 알려주세요.`;
}
case '/context': {
const ctx = await getConversationContext(env.DB, userId, chatId);
const remaining = config.threshold - ctx.recentMessages.length;
return `📊 <b>현재 컨텍스트</b>
분석된 메시지: ${ctx.previousSummary?.message_count ?? 0}
버퍼 메시지: ${ctx.recentMessages.length}
프로필 버전: ${ctx.previousSummary?.generation ?? 0}
총 메시지: ${ctx.totalMessages}
💡 ${remaining > 0 ? `${remaining}개 메시지 후 프로필 업데이트` : '업데이트 대기 중'}`;
버퍼: ${ctx.recentMessages.length}`;
}
case '/profile':
@@ -83,6 +109,162 @@ ${summary.summary}
버퍼 대기: ${ctx.recentMessages.length}`;
}
case '/domain': {
const domains = await env.DB
.prepare(`
SELECT domain, created_at
FROM user_domains
WHERE user_id = ? AND verified = 1
ORDER BY created_at DESC
`)
.bind(userId)
.all<{ domain: string; created_at: string }>();
if (!domains.results || domains.results.length === 0) {
return `🌐 <b>내 도메인</b>
등록된 도메인이 없습니다.
"도메인 등록" 또는 "example.com 등록"으로 시작하세요!`;
}
const domainList = domains.results.map((d, i) => {
const date = new Date(d.created_at).toLocaleDateString('ko-KR');
return `${i + 1}. ${d.domain} (${date})`;
}).join('\n');
return `🌐 <b>내 도메인</b> (${domains.results.length}개)
${domainList}
도메인 관리: "도메인명 네임서버 변경"`;
}
case '/server': {
// Cloud Orchestrator API를 통해 스펙 정보 포함된 서버 목록 조회
const telegramUserId = chatId; // chatId가 실제로는 telegram_user_id
interface ServerWithSpecs {
id: number;
label: string | null;
status: string;
region: string;
vcpu?: number;
memory_gb?: number;
bandwidth_tb?: number;
spec_name?: string;
}
let servers: ServerWithSpecs[] = [];
if (env.CLOUD_ORCHESTRATOR) {
try {
const response = await env.CLOUD_ORCHESTRATOR.fetch(`https://internal/api/provision/orders?user_id=${telegramUserId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
const data = await response.json() as { orders?: ServerWithSpecs[] };
servers = data.orders || [];
}
} catch {
// API 실패 시 로컬 DB 폴백
}
}
// API 실패 시 로컬 DB에서 조회 (스펙 정보 없이)
if (servers.length === 0) {
const localServers = await env.DB
.prepare(`
SELECT id, label, status, region
FROM server_orders
WHERE telegram_user_id = ? AND status IN ('active', 'stopped', 'provisioning')
ORDER BY created_at DESC
`)
.bind(telegramUserId)
.all<{ id: number; label: string; status: string; region: string }>();
servers = localServers.results || [];
}
if (servers.length === 0) {
return `🖥️ <b>내 서버</b>
보유한 서버가 없습니다.
"서버 추천" 또는 "서버 신청"으로 시작하세요!`;
}
const statusIcon: Record<string, string> = {
active: '🟢',
stopped: '🔴',
provisioning: '🟡',
};
const serverList = servers
.filter(s => ['active', 'stopped', 'provisioning'].includes(s.status))
.map((s) => {
const icon = statusIcon[s.status] || '⚪';
const label = s.label || '(이름없음)';
// 스펙 정보가 있으면 표시
let specInfo = '';
if (s.vcpu && s.memory_gb) {
specInfo = `\n ${s.vcpu}vCPU / ${s.memory_gb}GB RAM`;
if (s.bandwidth_tb) {
specInfo += ` / ${s.bandwidth_tb}TB`;
}
}
return `#${s.id} ${icon} <b>${label}</b> (${s.region})${specInfo}`;
}).join('\n\n');
return `🖥️ <b>내 서버</b> (${servers.filter(s => ['active', 'stopped', 'provisioning'].includes(s.status)).length}개)
${serverList}
서버 관리: "N번 시작/중지" 또는 "#N 재시작"`;
}
case '/security': {
return `🛡️ <b>DDoS 방어 서비스</b>
<b>AnvilShield</b> - 엔터프라이즈급 DDoS 방어
• L3/L4 네트워크 공격 방어
• L7 애플리케이션 공격 방어
• 실시간 트래픽 모니터링
• 자동 위협 탐지 및 차단
<b>요금제:</b>
• Basic: 10Gbps 방어 - ₩99,000/월
• Pro: 100Gbps 방어 - ₩299,000/월
• Enterprise: 무제한 - 별도 문의
🔜 <i>서비스 준비 중입니다. 문의: @AnvilSupport</i>`;
}
case '/phantomx': {
return `🔐 <b>PhantomX VPN</b>
<b>Xray 기반 차세대 보안 VPN</b>
• 🚀 초고속 연결 (Xray-core 엔진)
• 👻 트래픽 위장 (탐지 우회)
• 🌍 글로벌 서버 (한국/일본/미국/유럽)
• 📱 멀티 디바이스 지원
• 🔒 제로 로그 정책
<b>요금제:</b>
• 월간: ₩9,900/월
• 연간: ₩79,000/년 (33% 할인)
🔜 <i>서비스 준비 중입니다. 문의: @AnvilSupport</i>`;
}
case '/debug': {
// Admin only - exposes internal debug info
const adminId = env.DEPOSIT_ADMIN_ID ? parseInt(env.DEPOSIT_ADMIN_ID, 10) : null;