MySQL 테이블 행 추가/수정만으로 API를 생성하고 관리하는 동적 API 엔진입니다.
- 코드 배포 없이 API 생성: DB에 행을 추가하면 즉시 새 API 엔드포인트 활성화
- 다중 데이터소스 지원: MySQL, BigQuery, OpenSearch 등 다양한 데이터소스
- 복잡한 쿼리 지원: 다중 쿼리, 파이프라인 처리
- 버전 관리: 모든 변경 사항을 버전으로 관리, 언제든 롤백 가능
- 감사 로그: 모든 변경 이력을 자동 기록
- 보안: SQL Injection 방지, Soft Delete, API 키 인증
- 🧠 AI 기능: LLM 기반 API 생성, SQL 최적화, 테스트 케이스 생성, 자연어 API 호출
┌─────────────────────────────────────────────────────────────────────────────┐
│ 클라이언트 요청 │
│ GET /api/users/list?limit=10 │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Universal Router │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. 요청 경로(/api/users/list)와 메서드(GET)로 API 메타데이터 조회 │ │
│ │ 2. APP_API_ROUTE_L 테이블에서 매칭되는 라우트 검색 │ │
│ │ 3. USE_YN='Y', DEL_YN='N' 조건으로 활성 API만 처리 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Version Resolver │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ APP_API_VERSION_H 테이블에서 현재 버전(CRNT_YN='Y') 로직 조회 │ │
│ │ → LOGIC_TYPE, LOGIC_BODY, REQ_SPEC, RESP_SPEC 획득 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Validator Service │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ REQ_SPEC 기반으로 입력 파라미터 검증 │ │
│ │ - 필수값 체크 (required: true) │ │
│ │ - 타입 검증 (string, int, float, bool, list) │ │
│ │ - 기본값 적용 (default) │ │
│ │ - SQL Injection 위험 패턴 검사 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Executor Service │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ LOGIC_TYPE에 따라 적절한 실행기 선택: │ │
│ │ │ │
│ │ SQL → MySQL 쿼리 실행 (파라미터 바인딩) │ │
│ │ MULTI_SQL → 다중 쿼리 순차 실행 │ │
│ │ PIPELINE → 여러 스텝 연결 실행 │ │
│ │ BIGQUERY → Google BigQuery 쿼리 │ │
│ │ OPENSEARCH → OpenSearch 검색 │ │
│ │ HTTP_CALL → 외부 API 호출 │ │
│ │ PYTHON_EXPR → Python 표현식 실행 (제한적) │ │
│ │ STATIC → 정적 JSON 반환 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Response Mapper │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ RESP_SPEC에 따라 응답 형식 변환 │ │
│ │ - 필드명 매핑 │ │
│ │ - 데이터 타입 변환 (datetime → ISO string) │ │
│ │ - 중첩 구조 처리 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ JSON 응답 반환 │
│ {"data": [...], "count": 10} │
└─────────────────────────────────────────────────────────────────────────────┘
# app/routers/universal_router.py
@router.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
async def handle_request(path: str, request: Request, db: AsyncSession):
# 요청 경로와 메서드 추출
method = request.method # "GET"
full_path = f"/api/{path}" # "/api/users/list"모든 /api/* 경로로 들어오는 요청은 Universal Router가 받습니다. 하드코딩된 엔드포인트가 아니라, DB에서 동적으로 라우팅합니다.
-- 요청 경로와 메서드로 API 정의 검색
SELECT * FROM APP_API_ROUTE_L
WHERE API_PATH = '/api/users/list'
AND HTTP_MTHD = 'GET'
AND USE_YN = 'Y'
AND DEL_YN = 'N';APP_API_ROUTE_L 테이블에서 활성화된 API 정의를 찾습니다.
-- 현재 활성 버전의 로직 조회
SELECT * FROM APP_API_VERSION_H
WHERE ROUTE_ID = 'route-xxx'
AND CRNT_YN = 'Y';APP_API_VERSION_H 테이블에서 CRNT_YN='Y'인 현재 버전의 로직을 가져옵니다.
# REQ_SPEC 예시
{
"limit": {"type": "int", "required": false, "default": 10},
"cmpny_id": {"type": "string", "required": true}
}
# 검증 과정
1. cmpny_id가 없으면 → 400 에러 ("필수 파라미터 누락")
2. limit이 없으면 → 기본값 10 적용
3. limit="abc" 이면 → 400 에러 ("타입 불일치")
4. SQL Injection 패턴 검사 → "'; DROP TABLE" 감지 시 차단# LOGIC_TYPE: "SQL"
# LOGIC_BODY: "SELECT * FROM APP_USER_L WHERE CMPNY_ID = :cmpny_id LIMIT :limit"
# 실행 (파라미터 바인딩으로 SQL Injection 방지)
result = await db.execute(
text("SELECT * FROM APP_USER_L WHERE CMPNY_ID = :cmpny_id LIMIT :limit"),
{"cmpny_id": "company-001", "limit": 10}
){
"success": true,
"data": [
{"USER_ID": "user-001", "FIRST_NAME": "홍", "LAST_NAME": "길동", ...},
{"USER_ID": "user-002", "FIRST_NAME": "김", "LAST_NAME": "철수", ...}
],
"count": 2,
"execution_time_ms": 45.2
}새 API를 만들려면 코드 변경 없이 DB에 행만 추가하면 됩니다.
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 관리자 요청 │────▶│ Admin Router │────▶│ DB에 저장 │
│ POST /admin/ │ │ (검증 + 저장) │ │ │
│ routes │ │ │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
┌─────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────┐
│ APP_API_ROUTE_L (새 행 추가) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ROUTE_ID: "route-new-001" │ │
│ │ API_PATH: "/api/products/list" │ │
│ │ HTTP_MTHD: "GET" │ │
│ │ API_NAME: "상품 목록 조회" │ │
│ │ USE_YN: "Y" │ │
│ │ DEL_YN: "N" │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ APP_API_VERSION_H (새 행 추가) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ VERSION_ID: "ver-new-001" │ │
│ │ ROUTE_ID: "route-new-001" │ │
│ │ VERSION_NO: 1 │ │
│ │ CRNT_YN: "Y" ← 현재 활성 버전 │ │
│ │ LOGIC_TYPE: "SQL" │ │
│ │ LOGIC_BODY: "SELECT * FROM products LIMIT :limit" │ │
│ │ REQ_SPEC: {"limit": {"type": "int", "default": 20}} │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ APP_API_AUDIT_H (감사 로그 자동 생성) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ACTION: "CREATE" │ │
│ │ ROUTE_ID: "route-new-001" │ │
│ │ CREA_DT: "2026-01-09 18:30:00" │ │
│ │ CHANGE_NOTE: "신규 API 생성" │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ ✅ 즉시 사용 가능 │
│ GET /api/products/list?limit=10 │
│ (서버 재시작 불필요, 코드 배포 불필요) │
└──────────────────────────────────────────────────────────────────────┘
API 수정이 필요할 때는 기존 버전을 수정하지 않고 새 버전을 추가합니다.
┌─────────────────────────────────────────────────────────────────────────┐
│ 버전 관리 시나리오 │
└─────────────────────────────────────────────────────────────────────────┘
[초기 상태]
┌─────────────────────────────────────────┐
│ APP_API_VERSION_H │
├─────────────────────────────────────────┤
│ VERSION_NO: 1 │ CRNT_YN: Y ← 활성 │
│ LOGIC: SELECT * FROM users LIMIT 10 │
└─────────────────────────────────────────┘
│
│ 새 버전 추가 (POST /admin/routes/{id}/versions)
▼
[버전 2 추가 후]
┌─────────────────────────────────────────┐
│ APP_API_VERSION_H │
├─────────────────────────────────────────┤
│ VERSION_NO: 1 │ CRNT_YN: N ← 비활성 │ ← 기존 버전 보존
│ LOGIC: SELECT * FROM users LIMIT 10 │
├─────────────────────────────────────────┤
│ VERSION_NO: 2 │ CRNT_YN: Y ← 활성 │ ← 새 버전이 현재 버전
│ LOGIC: SELECT * FROM users │
│ WHERE del_yn='N' LIMIT 10 │
└─────────────────────────────────────────┘
│
│ 문제 발생! 롤백 필요 (PATCH /admin/routes/{id}/versions/1/activate)
▼
[버전 1로 롤백]
┌─────────────────────────────────────────┐
│ APP_API_VERSION_H │
├─────────────────────────────────────────┤
│ VERSION_NO: 1 │ CRNT_YN: Y ← 활성 │ ← 즉시 롤백 완료!
│ LOGIC: SELECT * FROM users LIMIT 10 │
├─────────────────────────────────────────┤
│ VERSION_NO: 2 │ CRNT_YN: N ← 비활성 │
│ LOGIC: SELECT * FROM users │
│ WHERE del_yn='N' LIMIT 10 │
└─────────────────────────────────────────┘
핵심 포인트:
- 기존 버전은 절대 수정/삭제되지 않음 (Immutable)
- 새 버전 추가 시 자동으로
CRNT_YN='Y'설정 - 롤백은
CRNT_YN플래그만 변경하면 즉시 적용 - 모든 버전 변경은
APP_API_AUDIT_H에 자동 기록
# LOGIC_BODY: "SELECT * FROM APP_USER_L WHERE CMPNY_ID = :cmpny_id"
# 파라미터: {"cmpny_id": "company-001"}
result = await db.execute(text(logic_body), params)
return [dict(row) for row in result.fetchall()]# LOGIC_BODY:
{
"queries": [
{"name": "company", "sql": "SELECT * FROM APP_CMPNY_L WHERE CMPNY_ID = :cmpny_id"},
{"name": "users", "sql": "SELECT * FROM APP_USER_L WHERE CMPNY_ID = :cmpny_id"},
{"name": "projects", "sql": "SELECT COUNT(*) as cnt FROM APP_PROJ_L WHERE CMPNY_ID = :cmpny_id"}
]
}
# 실행 결과:
{
"company": {...},
"users": [...],
"projects": {"cnt": 15}
}# LOGIC_BODY:
{
"steps": [
{"type": "SQL", "body": "SELECT COUNT(*) as total FROM APP_USER_L", "output": "user_count"},
{"type": "SQL", "body": "SELECT COUNT(*) as total FROM APP_PROJ_L", "output": "proj_count"},
{"type": "PYTHON_EXPR", "body": "$user_count.total + $proj_count.total", "output": "total"}
]
}
# Step 1 실행 → user_count = {"total": 100}
# Step 2 실행 → proj_count = {"total": 50}
# Step 3 실행 → total = 150 (이전 결과 참조)# LOGIC_BODY:
{
"url": "https://api.external.com/data",
"method": "GET",
"headers": {"Authorization": "Bearer $params.token"},
"timeout": 30
}
# 외부 API 호출 후 응답 반환
async with httpx.AsyncClient() as client:
response = await client.request(method, url, headers=headers)
return response.json()┌──────────────────────────────────────────────────────────────────────────────┐
│ LLM API 생성 프로세스 │
└──────────────────────────────────────────────────────────────────────────────┘
[1단계: 테이블 선택]
┌─────────────────────────────────────────┐
│ 사용자: APP_USER_L, APP_CMPNY_L 선택 │
└─────────────────────────────────────────┘
│
▼
[2단계: 스키마 정보 수집]
┌─────────────────────────────────────────────────────────────────────────────┐
│ GET /schema/tables/APP_USER_L │
│ { │
│ "columns": [ │
│ {"name": "USER_ID", "type": "VARCHAR(50)", "key": "PRI"}, │
│ {"name": "CMPNY_ID", "type": "VARCHAR(50)", "key": "MUL"}, │
│ {"name": "FIRST_NAME", "type": "VARCHAR(100)"}, │
│ ... │
│ ], │
│ "indexes": [{"name": "IX_CMPNY_ID", "columns": ["CMPNY_ID"]}], │
│ "sample_data": [{"USER_ID": "user-001", "FIRST_NAME": "홍", ...}] │
│ } │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
[3단계: 자연어 의도 입력]
┌─────────────────────────────────────────┐
│ 사용자: "회사별 사용자 수를 조회하는 │
│ API를 만들어줘" │
└─────────────────────────────────────────┘
│
▼
[4단계: LLM 프롬프트 생성]
┌─────────────────────────────────────────────────────────────────────────────┐
│ 시스템 프롬프트: │
│ "당신은 API 설계 전문가입니다. 다음 테이블 스키마를 참고하여 │
│ 사용자의 요청에 맞는 API 정의를 JSON으로 생성하세요." │
│ │
│ 테이블 스키마: │
│ - APP_USER_L: USER_ID(PK), CMPNY_ID(FK), FIRST_NAME, ... │
│ - APP_CMPNY_L: CMPNY_ID(PK), CMPNY_NAME, ... │
│ │
│ 사용자 요청: "회사별 사용자 수를 조회하는 API를 만들어줘" │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
[5단계: Vertex AI Gemini 호출]
┌─────────────────────────────────────────────────────────────────────────────┐
│ POST https://vertex-ai.googleapis.com/v1/... │
│ Authorization: Bearer (gcloud-key.json) │
│ Model: gemini-2.5-flash │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
[6단계: LLM 응답 (API 정의)]
┌─────────────────────────────────────────────────────────────────────────────┐
│ { │
│ "path": "/api/stats/users-by-company", │
│ "method": "GET", │
│ "name": "회사별 사용자 수 조회", │
│ "description": "각 회사별로 등록된 사용자 수를 집계합니다", │
│ "logic_type": "SQL", │
│ "logic_body": "SELECT c.CMPNY_NAME, COUNT(u.USER_ID) as user_count │
│ FROM APP_CMPNY_L c │
│ LEFT JOIN APP_USER_L u ON c.CMPNY_ID = u.CMPNY_ID │
│ WHERE c.DEL_YN = 'N' │
│ GROUP BY c.CMPNY_ID, c.CMPNY_NAME │
│ ORDER BY user_count DESC │
│ LIMIT :limit", │
│ "request_spec": { │
│ "limit": {"type": "int", "required": false, "default": 10} │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
[7단계: SQL 테스트 실행]
┌─────────────────────────────────────────────────────────────────────────────┐
│ POST /schema/test-sql │
│ 생성된 SQL을 실제 DB에서 테스트 실행 │
│ → 성공 시: "✅ 테스트 통과 (5건 조회, 32ms)" │
│ → 실패 시: "❌ 문법 오류: Unknown column..." │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
[8단계: API 등록]
┌─────────────────────────────────────────────────────────────────────────────┐
│ 테스트 통과 후 "API 생성" 버튼 클릭 │
│ → APP_API_ROUTE_L에 새 행 추가 │
│ → APP_API_VERSION_H에 새 행 추가 │
│ → 즉시 사용 가능: GET /api/stats/users-by-company │
└─────────────────────────────────────────────────────────────────────────────┘
사용자 질문: "최근 가입한 사용자 5명 보여줘"
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ [보안 검사 1단계] 악의적 의도 검사 │
│ check_question_intent() │
│ - "삭제해줘", "해킹", "비밀번호" 등 위험 키워드 감지 │
│ → 통과 ✓ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ [SQL 생성] LLM이 자연어 → SQL 변환 │
│ generate_sql_from_natural_language() │
│ │
│ 생성된 SQL: │
│ SELECT USER_ID, FIRST_NAME, LAST_NAME, CREA_DT │
│ FROM APP_USER_L │
│ WHERE DEL_YN = 'N' │
│ ORDER BY CREA_DT DESC │
│ LIMIT 5 │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ [보안 검사 2단계] SQL 보안 분석 │
│ check_sql_security() │
│ - DDL 명령어 (DROP, CREATE, ALTER) 검사 → 없음 ✓ │
│ - DML 명령어 (INSERT, UPDATE, DELETE) 검사 → 없음 ✓ │
│ - SQL Injection 패턴 검사 → 없음 ✓ │
│ - 민감 컬럼 (PASSWORD, TOKEN) 접근 검사 → 없음 ✓ │
│ - LIMIT 존재 확인 → 있음 ✓ │
│ → 결과: SAFE │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ [SQL 정제] sanitize_sql_query() │
│ - SELECT 문만 허용 확인 │
│ - LIMIT 값이 max_results 초과 시 조정 │
│ - 주석 제거, 정규화 │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ [실행] auto_execute=true 시 쿼리 실행 │
│ │
│ 결과: │
│ ┌───────────────┬────────────┬───────────┬─────────────────────┐ │
│ │ USER_ID │ FIRST_NAME │ LAST_NAME │ CREA_DT │ │
│ ├───────────────┼────────────┼───────────┼─────────────────────┤ │
│ │ user-001 │ 김 │ 기연 │ 2026-01-09 10:00:00 │ │
│ │ user-002 │ 최 │ 준혁 │ 2026-01-08 15:30:00 │ │
│ │ ... │ │ │ │ │
│ └───────────────┴────────────┴───────────┴─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
prompt-api-engine/
├── app/
│ ├── core/ # 핵심 설정 (config, database)
│ ├── models/ # SQLAlchemy 모델
│ ├── routers/ # FastAPI 라우터
│ ├── schemas/ # Pydantic 스키마
│ ├── services/ # 비즈니스 로직 (Executor, Validator)
│ └── main.py # 애플리케이션 엔트리포인트
├── scripts/ # 유틸리티 스크립트
├── requirements.txt
├── CHANGELOG.md
└── README.md
# 가상환경 생성 및 활성화
python -m venv venv
.\venv\Scripts\Activate
# 의존성 설치
pip install -r requirements.txt
# BigQuery 사용 시 (선택)
pip install google-cloud-bigquery.env 파일에 다음 설정 추가:
# MySQL
MYSQL_HOST=localhost
MYSQL_USER=root
MYSQL_PASSWORD=password
MYSQL_DB=cliwant
MYSQL_PORT=3306
# 관리자 API 키
API_KEY=your-admin-api-key
# BigQuery (선택)
GCP_PROJECT_ID=your-project-id
GCP_CREDENTIALS_PATH=gcloud-key.json
# OpenSearch (선택)
OPENSEARCH_HOST=https://localhost:9200
OPENSEARCH_USER=admin
OPENSEARCH_PASSWORD=admin# 테이블 생성
python scripts/create_tables.py
# 샘플 API 생성 (30개)
python scripts/insert_sample_apis.pyuvicorn app.main:app --reload접속 URL:
| URL | 설명 |
|---|---|
| http://localhost:8000 | API Tester UI (메인) |
| http://localhost:8000/docs | Swagger UI |
| http://localhost:8000/redoc | ReDoc |
| http://localhost:8000/admin/policy | Immutable 정책 조회 |
| 타입 | 설명 | 예시 | 상태 |
|---|---|---|---|
SQL |
단일 MySQL 쿼리 | SELECT * FROM users WHERE id = :id |
✅ 사용 가능 |
MULTI_SQL |
다중 쿼리 순차 실행 | 여러 테이블 조인 결과 조합 | ✅ 사용 가능 |
PIPELINE |
여러 로직 파이프라인 연결 | SQL → 변환 → 응답 | ✅ 사용 가능 |
BIGQUERY |
Google BigQuery 쿼리 | 대용량 데이터 분석 | ✅ 사용 가능 |
OPENSEARCH |
OpenSearch 검색 쿼리 | 전문 검색, 로그 분석 | ✅ 사용 가능 |
PYTHON_EXPR |
Python 표현식 | 간단한 데이터 변환 | ⛔ 비활성화 |
HTTP_CALL |
외부 API 호출 | 타 서비스 연동 | ✅ 사용 가능 |
STATIC_RESPONSE |
정적 JSON 응답 | 목업, 테스트용 | ✅ 사용 가능 |
⚠️ PYTHON_EXPR 비활성화: RCE(원격 코드 실행) 보안 위험으로 인해 v1.9.3부터 비활성화되었습니다. 대안으로MULTI_SQL,PIPELINE,STATIC_RESPONSE를 사용하세요.
| 컬럼 | 타입 | 설명 |
|---|---|---|
| ROUTE_ID | VARCHAR(50) | PK |
| API_PATH | VARCHAR(255) | API 경로 |
| HTTP_MTHD | VARCHAR(10) | HTTP 메서드 |
| API_NAME | VARCHAR(255) | API 이름 |
| USE_YN | CHAR(1) | 사용 여부 (Y/N) |
| DEL_YN | CHAR(1) | 삭제 여부 (Y/N) |
| 컬럼 | 타입 | 설명 |
|---|---|---|
| VERSION_ID | VARCHAR(50) | PK |
| ROUTE_ID | VARCHAR(50) | FK → APP_API_ROUTE_L |
| VERSION_NO | INT | 버전 번호 |
| CRNT_YN | CHAR(1) | 현재 버전 여부 |
| REQ_SPEC | JSON | 입력 파라미터 검증 규칙 |
| LOGIC_TYPE | VARCHAR(50) | 로직 타입 |
| LOGIC_BODY | TEXT | 실행할 로직 |
| RESP_SPEC | JSON | 응답 매핑 규칙 |
{
"logic_type": "SQL",
"logic_body": "SELECT * FROM APP_USER_L WHERE CMPNY_ID = :cmpny_id",
"request_spec": {
"cmpny_id": {"type": "string", "required": true}
}
}{
"logic_type": "MULTI_SQL",
"logic_body": {
"queries": [
{"name": "users", "sql": "SELECT * FROM APP_USER_L WHERE CMPNY_ID = :cmpny_id"},
{"name": "company", "sql": "SELECT * FROM APP_CMPNY_L WHERE CMPNY_ID = :cmpny_id"}
]
}
}{
"logic_type": "PIPELINE",
"logic_body": {
"steps": [
{"type": "SQL", "body": "SELECT COUNT(*) as cnt FROM APP_USER_L", "output": "user_count"},
{"type": "STATIC_RESPONSE", "body": "{\"total_users\": $params.user_count}"}
]
}
}{
"logic_type": "BIGQUERY",
"logic_body": "SELECT * FROM `project.dataset.table` WHERE date = @date LIMIT @limit",
"request_spec": {
"date": {"type": "string", "required": true},
"limit": {"type": "int", "default": 100}
}
}{
"logic_type": "OPENSEARCH",
"logic_body": {
"index": "logs-*",
"body": {
"query": {"match": {"message": "$params.keyword"}},
"size": 100
}
}
}Vertex AI Gemini 2.5/3.0을 활용한 강력한 AI 기능들:
자연어로 질문하면 AI가 적합한 API를 찾아 실행합니다.
POST /schema/ai/chat
{
"question": "최근 가입한 사용자 10명 보여줘",
"auto_execute": true,
"model": "vertex_ai/gemini-2.5-flash"
}응답 예시:
- 선택된 API:
GET /api/users/list - 추출된 파라미터:
{"limit": 10} - 신뢰도: 95%
- 자동 실행 결과 포함
SQL 쿼리를 분석하여 성능 개선 방안을 제안합니다.
POST /schema/ai/optimize-sql
{
"sql_query": "SELECT * FROM APP_USER_L WHERE CMPNY_ID = :cmpny_id",
"table_names": ["APP_USER_L"],
"execution_time_ms": 500,
"model": "vertex_ai/gemini-2.5-flash"
}제안 항목:
- 인덱스 활용 최적화
- 쿼리 재작성 추천
- JOIN 순서 최적화
- 새 인덱스 생성 권장
API 정의를 분석하여 포괄적인 테스트 케이스를 생성합니다.
POST /schema/ai/generate-test-cases
{
"route_id": "api-route-id",
"model": "vertex_ai/gemini-2.5-flash"
}생성 케이스 유형:
| 유형 | 설명 | 최소 개수 |
|---|---|---|
| Positive | 정상 동작 케이스 | 3개 |
| Negative | 에러 케이스 (필수값 누락 등) | 2개 |
| Boundary | 경계값 테스트 | 2개 |
| Performance | 성능 테스트 | 1개 |
| 기능 | 설명 | 버전 |
|---|---|---|
| Immutable 정책 | API 정의는 추가만 가능, 수정/삭제 불가 | v1.0 |
| SQL Injection 방지 | DROP, TRUNCATE 등 위험 키워드 차단, 파라미터 바인딩 강제 | v1.0 |
| 감사 로그 | 모든 변경 이력 기록 (누가, 언제, 무엇을) | v1.0 |
| 버전 관리 | 기존 버전 보존, 언제든 이전 버전으로 전환 가능 | v1.0 |
| CORS 설정 강화 | 환경변수 기반 도메인 제한 (CORS_ORIGINS) |
v1.9.3 |
| PYTHON_EXPR 비활성화 | RCE 공격 방지를 위해 eval() 실행 차단 | v1.9.3 |
| auto_execute 기본 비활성화 | Human-in-the-loop 강화 | v1.9.3 |
| 읽기 전용 DB 계정 | 자연어 SQL 쿼리용 별도 계정 분리 | v1.9.4 |
| SQL 실행 타임아웃 | 30초 기본 타임아웃, DoS 방지 | v1.9.4 |
| SQL Injection 정규화 | 주석/공백 변형 우회 공격 차단 | v1.9.4 |
| 민감 컬럼 마스킹 | password, token 등 LLM 노출 방지 | v1.9.4 |
자연어 SQL 쿼리 생성 기능에 대한 상세 보안 분석은 SECURITY_ASSESSMENT.md 문서를 참조하세요.
보안 개선 현황:
| 우선순위 | 항목 | 상태 |
|---|---|---|
| 🔴 P0 | CORS 설정 강화 | ✅ 완료 (v1.9.3) |
| 🔴 P0 | PYTHON_EXPR 비활성화 | ✅ 완료 (v1.9.3) |
| 🔴 P0 | 읽기 전용 DB 사용자 분리 | ✅ 완료 (v1.9.4) |
| 🔴 P0 | SQL 실행 타임아웃 | ✅ 완료 (v1.9.4) |
| 🟠 P1 | 민감 컬럼 서버 측 마스킹 | ✅ 완료 (v1.9.4) |
| 🟠 P1 | SQL Injection 정규화 | ✅ 완료 (v1.9.4) |
| 🔴 High | API Key 인증 강화 | 📋 추후 진행 |
| 🔴 High | JWT 인증 | 📋 추후 진행 |
| 🔴 High | Redis 캐싱 | 📋 추후 진행 |
| 🟡 Medium | RBAC 권한 관리 | 📋 추후 진행 |
| 🟡 Medium | Rate Limiting | 📋 추후 진행 |
ℹ️ P0 항목 대부분 완료됨. 프로덕션 배포 전 인증/캐싱 시스템 추가를 권장합니다.
API 정의 데이터의 무결성을 보장하기 위해 추가 전용(Append-only) 정책을 적용합니다:
| 리소스 | 허용 작업 | 금지 작업 |
|---|---|---|
APP_API_ROUTE_L |
CREATE, ACTIVATE, DEACTIVATE | UPDATE, DELETE |
APP_API_VERSION_H |
CREATE, SET_CURRENT | UPDATE, DELETE |
APP_API_AUDIT_H |
CREATE (자동) | UPDATE, DELETE |
장점:
- ✅ 실수로 인한 API 삭제 완전 방지
- ✅ 모든 변경 이력 영구 보존
- ✅ 언제든 이전 버전으로 즉시 전환
- ✅ 감사 추적 용이
# API 목록 조회 (공개)
curl http://localhost:8000/admin/routes
# 새 API 생성 (API 키 필요)
curl -X POST http://localhost:8000/admin/routes \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"path": "my-api", "method": "GET", "name": "My API"}'
# 새 버전 생성 (API 키 필요)
curl -X POST http://localhost:8000/admin/routes/{route_id}/versions \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"logic_type": "SQL",
"logic_body": "SELECT * FROM users LIMIT :limit",
"request_spec": {"limit": {"type": "int", "default": 10}},
"change_note": "초기 버전"
}'
# 상태 변경 (활성화/비활성화만 가능)
curl -X PATCH http://localhost:8000/admin/routes/{route_id}/status \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"is_active": false, "reason": "임시 비활성화"}'
# 현재 버전 변경
curl -X PATCH http://localhost:8000/admin/routes/{route_id}/versions/1/activate \
-H "X-API-Key: your-api-key"
# 정책 조회
curl http://localhost:8000/admin/policy| 카테고리 | API 수 | 예시 |
|---|---|---|
| 사용자 | 3 | /api/users/list, /api/users/by-company |
| 회사 | 3 | /api/companies/list, /api/companies/by-bizno |
| 프로젝트 | 6 | /api/projects/recent, /api/projects/active |
| 사전규격 | 3 | /api/prcr-projects/recent |
| 계약 | 4 | /api/contracts/recent, /api/contracts/by-bizno |
| 입찰계획 | 3 | /api/bid-plans/by-year |
| 면허 | 2 | /api/licenses/by-bizno |
| 검색 | 1 | /api/searches/list |
| 발주기관 | 2 | /api/clients/list |
| 다중쿼리 | 2 | /api/company/dashboard |
| 통계 | 1 | /api/stats/projects-by-type |
| 기본 | 3 | /api/hello, /api/echo, /api/users |
MIT License