Skip to content

Latest commit

 

History

History
377 lines (304 loc) · 14.9 KB

File metadata and controls

377 lines (304 loc) · 14.9 KB

playground-server 리라이트 계획: SQLite 기반 전체 DB 영속화

배경

현재 playground-server는 완전히 stateless(DB 없음, 메모리 전용). hackathon-server의 영속화 기능을 SQLite 기반 로컬/개인용으로 재구현해야 함. 인증 불필요. 벤더 관리(암호화된 API 키 포함) 필수. 각 세션이 어떤 벤더로 진행되었는지 추적 필수.


1. 새 인터페이스 타입 (packages/interface/src/playground/)

IAutoBePlaygroundStoredVendor.ts (신규)

export interface IAutoBePlaygroundStoredVendor {
  id: string;            // UUID
  name: string;          // 사람이 읽을 수 있는 라벨 ("My OpenAI", "로컬 Ollama")
  model: string;         // "gpt-4.1", "claude-3-sonnet" 등
  baseURL: string | null;
  semaphore: number;     // 기본값 16
  created_at: string;    // ISO datetime
}
export namespace IAutoBePlaygroundStoredVendor {
  export interface ICreate {
    name: string;
    model: string;
    apiKey: string;       // 평문으로 보내면 서버가 암호화하여 저장
    baseURL?: string | null;
    semaphore?: number;
  }
  export interface IUpdate {
    name?: string;
    model?: string;
    apiKey?: string;      // 선택사항, 제공 시 재암호화
    baseURL?: string | null;
    semaphore?: number;
  }
}

IAutoBePlaygroundSession.ts (신규)

IAutoBeHackathonSession을 기반으로 하되 participant/hackathon 제거:

export interface IAutoBePlaygroundSession extends IAutoBePlaygroundSession.ISummary {
  histories: AutoBeHistory[];
  event_snapshots: AutoBeEventSnapshot[];
}
export namespace IAutoBePlaygroundSession {
  export interface ISummary {
    id: string;
    vendor: IAutoBePlaygroundStoredVendor;  // 사용된 벤더 설정
    title: string | null;
    locale: string;
    timezone: string;
    phase: AutoBePhase | null;
    token_usage: IAutoBeTokenUsageJson;
    created_at: string;   // ISO datetime
    completed_at: string | null;
  }
  export interface ICreate {
    vendor_id: string;    // 저장된 벤더 참조
    locale: string;
    timezone: string;
    title?: string | null;
  }
  export interface IUpdate {
    title: string | null;
  }
}

packages/interface/src/playground/index.ts에서 export 추가.


2. Prisma 스키마 (apps/playground-server/prisma/schema/main.prisma)

datasource db {
  provider = "sqlite"
  url      = env("PLAYGROUND_DATABASE_URL")
}

generator client {
  provider     = "prisma-client"
  output       = "../../src/prisma"
  moduleFormat = "cjs"
}

model autobe_playground_vendors {
  id                String    @id
  name              String
  model             String
  encrypted_api_key String    // AES-256-GCM 암호화
  base_url          String?
  semaphore         Int       @default(16)
  created_at        DateTime
  deleted_at        DateTime?

  sessions autobe_playground_sessions[]
  @@index([created_at])
}

model autobe_playground_sessions {
  id                          String    @id
  autobe_playground_vendor_id String
  locale                      String
  timezone                    String
  title                       String?
  created_at                  DateTime
  completed_at                DateTime?
  deleted_at                  DateTime?

  vendor      autobe_playground_vendors              @relation(fields: [autobe_playground_vendor_id], references: [id])
  connections autobe_playground_session_connections[]
  histories   autobe_playground_session_histories[]
  events      autobe_playground_session_events[]
  aggregate   autobe_playground_session_aggregates?

  @@index([autobe_playground_vendor_id, created_at])
}

model autobe_playground_session_connections {
  id                           String    @id
  autobe_playground_session_id String
  created_at                   DateTime
  disconnected_at              DateTime?

  session    autobe_playground_sessions              @relation(fields: [autobe_playground_session_id], references: [id], onDelete: Cascade)
  histories  autobe_playground_session_histories[]
  events     autobe_playground_session_events[]

  @@index([autobe_playground_session_id, created_at])
}

model autobe_playground_session_histories {
  id                                      String   @id
  autobe_playground_session_id            String
  autobe_playground_session_connection_id String
  type                                    String
  data                                    String   // JSON
  created_at                              DateTime

  session    autobe_playground_sessions             @relation(fields: [autobe_playground_session_id], references: [id], onDelete: Cascade)
  connection autobe_playground_session_connections   @relation(fields: [autobe_playground_session_connection_id], references: [id], onDelete: Cascade)

  @@index([autobe_playground_session_id, created_at])
  @@index([autobe_playground_session_connection_id, created_at])
}

model autobe_playground_session_events {
  id                                      String   @id
  autobe_playground_session_id            String
  autobe_playground_session_connection_id String
  type                                    String
  data                                    String   // JSON
  created_at                              DateTime

  session    autobe_playground_sessions             @relation(fields: [autobe_playground_session_id], references: [id], onDelete: Cascade)
  connection autobe_playground_session_connections   @relation(fields: [autobe_playground_session_connection_id], references: [id], onDelete: Cascade)

  @@index([autobe_playground_session_id, created_at])
  @@index([autobe_playground_session_connection_id, created_at])
}

model autobe_playground_session_aggregates {
  id                           String  @id
  autobe_playground_session_id String  @unique
  phase                        String?
  enabled                      Boolean
  token_usage                  String  // JSON

  session autobe_playground_sessions @relation(fields: [autobe_playground_session_id], references: [id], onDelete: Cascade)
}

3. 소스 파일 구조 (apps/playground-server/src/)

핵심 인프라

파일 기반 용도
AutoBePlaygroundConfiguration.ts AutoBeHackathonConfiguration.ts 환경설정 (포트, 컴파일러 수, 암호화 키, DB URL)
AutoBePlaygroundGlobal.ts AutoBeHackathonGlobal.ts Prisma 싱글톤 (SQLite, 어댑터 불필요)
AutoBePlaygroundModule.ts 재작성 새 컨트롤러 등록
AutoBePlaygroundServer.ts 재작성 설정에서 포트 읽기

컨트롤러

파일 기반 용도
controllers/HealthCheckController.ts 기존 유지 헬스체크
controllers/AutoBePlaygroundVendorController.ts 신규 벤더 CRUD REST API
controllers/AutoBePlaygroundSessionController.ts 해커톤 참가자 세션 컨트롤러 세션 CRUD REST API (인증 데코레이터 없음)
controllers/AutoBePlaygroundSessionSocketController.ts 해커톤 참가자 세션 소켓 컨트롤러 WS connect/replay

프로바이더

파일 기반 용도
providers/AutoBePlaygroundVendorProvider.ts 신규 벤더 CRUD + API 키 암호화/복호화
providers/sessions/AutoBePlaygroundSessionProvider.ts AutoBeHackathonSessionProvider.ts 세션 CRUD (hackathon/participant 없음, 모델 제한 없음)
providers/sessions/AutoBePlaygroundSessionConnectionProvider.ts AutoBeHackathonSessionConnectionProvider.ts 연결 추적
providers/sessions/AutoBePlaygroundSessionEventProvider.ts AutoBeHackathonSessionEventProvider.ts 이벤트 영속화
providers/sessions/AutoBePlaygroundSessionHistoryProvider.ts AutoBeHackathonSessionHistoryProvider.ts 히스토리 영속화
providers/sessions/AutoBePlaygroundSessionSocketProvider.ts AutoBeHackathonSessionSocketProvider.ts WS 오케스트레이션 (인증 없이 세션만 조회)
providers/sessions/acceptors/AutoBePlaygroundSessionSocketAcceptor.ts AutoBeHackathonSessionSocketAcceptor.ts 핵심 WS 로직 (connect, replay)
providers/sessions/acceptors/AutoBePlaygroundSessionCompiler.ts AutoBeHackathonSessionCompiler.ts 컴파일러 워커 풀

유틸리티

파일 기반 용도
utils/PaginationUtil.ts 해커톤에서 복사 페이지네이션 유틸
utils/CryptoUtil.ts 신규 AES-256-GCM API 키 암호화/복호화
structures/IEntity.ts 해커톤에서 복사 { id: string } 베이스 인터페이스

실행파일

파일 기반 용도
executable/server.ts 재작성 설정 사용
executable/compiler.ts 기존 유지 컴파일러 워커
executable/schema.ts 신규 SQLite 마이그레이션 셋업

4. 삭제할 파일

  • src/controllers/AutoBePlaygroundController.ts → 세션 소켓 컨트롤러로 대체
  • src/controllers/AutoBePlaygroundReplayController.ts → 세션 소켓 replay로 대체
  • src/providers/AutoBePlaygroundProvider.ts → 세션 프로바이더들로 대체
  • src/providers/AutoBePlaygroundAcceptor.ts → acceptor로 대체
  • src/providers/AutoBePlaygroundReplayProvider.ts → DB 기반 replay로 대체

5. REST API 엔드포인트

벤더 CRUD (/autobe/playground/vendors)

메서드 경로 Body 응답 용도
POST / ICreate IAutoBePlaygroundStoredVendor 벤더 생성
PATCH / IPage.IRequest IPage<IAutoBePlaygroundStoredVendor> 벤더 목록 (페이지네이션)
GET /:id - IAutoBePlaygroundStoredVendor 벤더 조회
PUT /:id IUpdate - 벤더 수정
DELETE /:id - - 소프트 삭제

주의: API 키는 응답에 절대 포함되지 않음.

세션 CRUD (/autobe/playground/sessions)

메서드 경로 Body 응답 용도
POST / ICreate IAutoBePlaygroundSession 세션 생성
PATCH / IPage.IRequest IPage<ISummary> 세션 목록
GET /:id - IAutoBePlaygroundSession 세션 상세 조회
PUT /:id IUpdate - 제목 수정
DELETE /:id - - 소프트 삭제

세션 WebSocket (/autobe/playground/sessions)

메서드 경로 Header 용도
WS /:id/connect (없음) 라이브 에이전트 세션
WS /:id/replay (없음) 세션 이벤트 재생

6. API 키 암호화 (utils/CryptoUtil.ts)

Node.js 내장 crypto 모듈, AES-256-GCM 사용:

export namespace CryptoUtil {
  // 키 출처: 환경변수 PLAYGROUND_ENCRYPTION_KEY (32바이트 hex)
  export const encrypt = (plaintext: string): string => { ... }
  export const decrypt = (ciphertext: string): string => { ... }
}
  • iv:authTag:ciphertext 형식으로 저장 (모두 base64)
  • PLAYGROUND_ENCRYPTION_KEY 환경변수에서 키 로드

7. 환경설정 (AutoBePlaygroundConfiguration.ts)

interface IEnvironments {
  PLAYGROUND_API_PORT: `${number}`;           // 기본값 5890
  PLAYGROUND_COMPILERS: `${number}`;          // 컴파일러 워커 풀 크기
  PLAYGROUND_TIMEOUT?: `${number}` | "NULL";
  PLAYGROUND_DATABASE_URL: string;            // "file:./autobe-playground.db"
  PLAYGROUND_ENCRYPTION_KEY: string;          // AES-256용 32바이트 hex 키
}

8. WebSocket 흐름 (connect)

  1. 클라이언트가 REST POST /autobe/playground/sessions로 세션 생성 (vendor_id 참조)
  2. 클라이언트가 WS /autobe/playground/sessions/:id/connect로 접속
  3. 서버가 세션 + 벤더 조회 (API 키 복호화)
  4. connection 레코드 생성
  5. 히스토리가 있으면 → 과거 이벤트 재생, 새 이벤트 폴링 (2.5초 간격)
  6. 복호화된 벤더 설정으로 AutoBeAgent 생성
  7. 이벤트 리스너 등록 → DB 영속화
  8. RPC 연결 수락
  9. 대화 완료 시 → 히스토리 저장, enabled=true 설정

9. 추가할 의존성 (package.json)

"uuid": "...",
"dotenv": "...",
"dotenv-expand": "...",
"@nestia/e2e": "..."    // ArrayUtil, RandomGenerator용

Prisma는 SQLite 네이티브 프로바이더 사용 (별도 어댑터 불필요).


10. 구현 순서

  1. 인터페이스 타입IAutoBePlaygroundStoredVendor, IAutoBePlaygroundSession (packages/interface/src/playground/)
  2. Prisma 스키마apps/playground-server/prisma/schema/main.prisma
  3. 인프라 — Configuration, Global, CryptoUtil, PaginationUtil, IEntity
  4. 벤더 프로바이더 + 컨트롤러 — 암호화 포함 CRUD
  5. 세션 프로바이더 + 컨트롤러 — REST CRUD
  6. 세션 하위 프로바이더 — Connection, Event, History 프로바이더
  7. 소켓 acceptor + 컴파일러 — 핵심 WS 로직
  8. 소켓 프로바이더 + 컨트롤러 — WS 오케스트레이션
  9. Module + Server 재작성 — 전부 와이어링
  10. 기존 파일 삭제 — 대체된 컨트롤러/프로바이더 제거
  11. 실행파일 — server.ts, schema.ts 업데이트

11. 설계 결정 사항

  • 벤더 삭제: Soft delete만 사용 (벤더, 세션 모두). Cascade도 Restrict도 아님. 일관된 패턴.
  • Simulate: 제외. connectreplay WS 엔드포인트만. 개인용이니 심플하게.
  • Report: 제외. 개인 playground에 불필요.
  • Review 시스템: 제외. review_article_url, completed_at 로직 없음.

12. hackathon-server와 주요 차이점

항목 hackathon-server playground-server
DB PostgreSQL + @prisma/adapter-pg SQLite (Prisma 네이티브)
인증 JWT + bcrypt 없음
Hackathon/Participant 모든 쿼리에 필수 완전 제거
벤더 환경변수에 하드코딩 (OPENAI_API_KEY 등) DB 저장 + 암호화된 키
모델 제한 모델별 10/3/1 제한 없음
Review 시스템 review_article_url + completed_at 제거
Simulate 벤치마크에서 MockAgent 제거 (심플하게)
시딩 CSV 참가자 + 예제 세션 불필요
리포트 마크다운 리포트 생성기 제거
세션 locale "en-US" 하드코딩 세션별 저장

13. 검증

  1. pnpm run build — TypeScript 컴파일 통과
  2. .env 생성: PLAYGROUND_DATABASE_URL, PLAYGROUND_ENCRYPTION_KEY, PLAYGROUND_API_PORT, PLAYGROUND_COMPILERS
  3. ts-node src/executable/schema.ts 실행 → SQLite DB 생성 + 마이그레이션
  4. 서버 시작: ts-node src/executable/server.ts
  5. REST로 벤더 CRUD 테스트
  6. REST로 세션 생성 테스트
  7. tgrid 클라이언트로 WebSocket connect 테스트
  8. SQLite에 이벤트/히스토리 영속화 확인
  9. replay WS 테스트
  10. nestia sdk 실행하여 playground-api 재생성 (src/api/functional절대 수동 편집 금지)