refactor: migrate server provisioning to Cloud Orchestrator service

- Remove Queue-based server provisioning (moved to cloud-orchestrator)
- Add manage_server tool with Service Binding to Cloud Orchestrator
- Add CDN cache hit rate estimation based on tech_stack
- Always display bandwidth info (show "포함 범위 내" when no overage)
- Add language auto-detection (ko, ja, zh, en)
- Update system prompt to always call tools fresh
- Add Server System documentation to CLAUDE.md

BREAKING: Server provisioning now requires cloud-orchestrator service

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-26 12:26:21 +09:00
parent 5413605347
commit 87c92e1ed1
27 changed files with 695 additions and 4584 deletions

View File

@@ -1,208 +0,0 @@
/**
* KV 기반 세션 관리 유틸리티
* - 다단계 플로우 (서버 주문, 도메인 등록 등)의 임시 데이터 저장
* - TTL 24시간 자동 만료
*/
export type SessionType = 'server_order' | 'domain_register';
export interface SessionData<T = unknown> {
type: SessionType;
step: string;
data: T;
userId: number;
createdAt: number;
updatedAt: number;
}
// 서버 주문 세션 데이터
export interface ServerOrderSessionData {
// 추천 목록 (선택 전까지 임시 저장)
recommendations?: Array<{
plan: string;
region: string;
provider: string;
}>;
// 추천 정보
purpose?: string;
budget?: number;
expectedUsers?: number;
// 선택된 사양
plan?: string;
provider?: string;
region?: string;
// OS 선택
image?: string;
// 가격 (캐시)
priceKrw?: number;
// 주문 ID (최종 확인 단계)
orderId?: number;
}
// 서버 주문 단계 정의 (step 필드 타입 가이드)
export type ServerOrderStep = 'recommend' | 'spec_confirm' | 'os_select' | 'final_confirm';
const SESSION_TTL_SECONDS = 24 * 60 * 60; // 24시간
/**
* 세션 ID 생성
* format: {type_prefix}_{userId}_{random}
*/
function generateSessionId(type: SessionType, userId: number): string {
const prefix = type === 'server_order' ? 'srv' : 'dom';
const random = crypto.randomUUID().slice(0, 8);
return `${prefix}_${userId}_${random}`;
}
/**
* 세션 생성
*/
export async function createSession<T>(
kv: KVNamespace,
userId: number,
type: SessionType,
initialData: T,
step: string = 'init'
): Promise<string> {
const sessionId = generateSessionId(type, userId);
const now = Date.now();
const session: SessionData<T> = {
type,
step,
data: initialData,
userId,
createdAt: now,
updatedAt: now,
};
await kv.put(
`session:${sessionId}`,
JSON.stringify(session),
{ expirationTtl: SESSION_TTL_SECONDS }
);
// 사용자의 활성 세션 참조 저장 (같은 타입의 이전 세션 덮어쓰기)
await kv.put(
`user_session:${userId}:${type}`,
sessionId,
{ expirationTtl: SESSION_TTL_SECONDS }
);
return sessionId;
}
/**
* 세션 조회
*/
export async function getSession<T>(
kv: KVNamespace,
sessionId: string
): Promise<SessionData<T> | null> {
const raw = await kv.get(`session:${sessionId}`);
if (!raw) return null;
try {
return JSON.parse(raw) as SessionData<T>;
} catch {
return null;
}
}
/**
* 세션 조회 + 권한 검증
*/
export async function getSessionForUser<T>(
kv: KVNamespace,
sessionId: string,
userId: number
): Promise<SessionData<T> | null> {
const session = await getSession<T>(kv, sessionId);
if (!session) return null;
if (session.userId !== userId) return null;
return session;
}
/**
* 사용자의 활성 세션 조회
*/
export async function getUserActiveSession<T>(
kv: KVNamespace,
userId: number,
type: SessionType
): Promise<{ sessionId: string; session: SessionData<T> } | null> {
const sessionId = await kv.get(`user_session:${userId}:${type}`);
if (!sessionId) return null;
const session = await getSession<T>(kv, sessionId);
if (!session) {
// 참조는 있지만 세션이 만료됨 - 참조 정리
await kv.delete(`user_session:${userId}:${type}`);
return null;
}
return { sessionId, session };
}
/**
* 세션 업데이트
*/
export async function updateSession<T>(
kv: KVNamespace,
sessionId: string,
updates: Partial<T> & { step?: string }
): Promise<SessionData<T> | null> {
const session = await getSession<T>(kv, sessionId);
if (!session) return null;
const { step, ...dataUpdates } = updates;
const updated: SessionData<T> = {
...session,
step: step ?? session.step,
data: { ...session.data, ...dataUpdates } as T,
updatedAt: Date.now(),
};
await kv.put(
`session:${sessionId}`,
JSON.stringify(updated),
{ expirationTtl: SESSION_TTL_SECONDS }
);
return updated;
}
/**
* 세션 삭제
*/
export async function deleteSession(
kv: KVNamespace,
sessionId: string
): Promise<void> {
const session = await getSession(kv, sessionId);
await kv.delete(`session:${sessionId}`);
// 사용자 참조도 삭제
if (session) {
await kv.delete(`user_session:${session.userId}:${session.type}`);
}
}
/**
* 세션 만료 여부 확인 (UI용 메시지)
*/
export function isSessionExpired(session: SessionData | null): boolean {
if (!session) return true;
const elapsed = Date.now() - session.createdAt;
return elapsed > SESSION_TTL_SECONDS * 1000;
}