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:
214
src/commands.ts
214
src/commands.ts
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user