zioinfo-mail/workspace/guardia-itsm/routers/query_history.py
DESKTOP-TKLFCPR\ython b8faec44e0 feat(advanced): GUARDiA 고급 확장 구현 — 20 routers + 754 endpoints
CMDB 자동 발견 (4개):
- autodiscovery.py: SSH 네트워크 스캔 + CMDB 자동 등록
- snmp_discovery.py: SNMP v2c/v3 장비 자동 발견
- dependency_map.py: 서비스 의존성 자동 매핑 (netstat)
- config_inventory.py: 서버 인벤토리 자동 수집 (SSH)

NL 쿼리 엔진 (3개):
- nlquery.py: Text-to-SQL (SELECT 전용, DML 차단)
- op_assistant.py: Multi-turn 대화형 운영 어시스턴트
- query_history.py: 쿼리 이력·즐겨찾기·공유

구성 드리프트 (3개):
- drift_detection.py: 골든 구성 vs 실제 비교·SR 자동 생성
- golden_config.py: 내장 CSAP 템플릿 + 버전 관리
- auto_remediation.py: 승인 기반 자동 교정 + 롤백

멀티클라우드 (4개):
- multicloud.py: 통합 관제 (NCloud+AWS+KT)
- aws_connector.py: AWS SigV4 직접 서명 연동
- cost_optimizer.py: AI 비용 최적화 권고
- cloud_migration.py: On-prem→K-Cloud 체크리스트

공공기관 특화 (6개):
- narasajang.py: 나라장터 OpenAPI 연동
- public_api_hub.py: data.go.kr KISA·기상청 허브
- isp_support.py: ISP 수립 지원 + AI 보고서
- network_zone.py: 행정망/인터넷망 분리 관리
- k_cloud.py: 정부 K-Cloud 전환 자동화
- e_procurement.py: 전자조달 계약·검수·납품

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 14:33:41 +09:00

176 lines
4.9 KiB
Python

"""
쿼리 이력·즐겨찾기·공유 대시보드
운영자가 자주 쓰는 쿼리를 저장하고 팀과 공유.
엔드포인트:
GET /api/queryhistory/ — 내 쿼리 이력
GET /api/queryhistory/favorites — 즐겨찾기
POST /api/queryhistory/{id}/favorite — 즐겨찾기 토글
POST /api/queryhistory/save — 쿼리 직접 저장
GET /api/queryhistory/shared — 팀 공유 쿼리
POST /api/queryhistory/{id}/share — 공유 토글
DELETE /api/queryhistory/{id} — 이력 삭제
"""
from __future__ import annotations
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select, desc
from sqlalchemy.ext.asyncio import AsyncSession
from core.auth import get_current_user
from database import get_db
from models import User, QueryHistory
router = APIRouter(prefix="/api/queryhistory", tags=["쿼리 이력"])
class SaveQueryRequest(BaseModel):
question: str
sql: str
description: Optional[str] = None
@router.get("/")
async def list_history(
limit: int = 50,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
rows = await db.execute(
select(QueryHistory).where(QueryHistory.user_id == user.id)
.order_by(desc(QueryHistory.executed_at)).limit(limit)
)
hs = rows.scalars().all()
return [
{"id": h.id, "question": h.question, "sql": h.generated_sql,
"rows": h.row_count, "is_favorite": h.is_favorite,
"is_shared": h.is_shared, "executed_at": h.executed_at}
for h in hs
]
@router.get("/favorites")
async def list_favorites(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
rows = await db.execute(
select(QueryHistory).where(
QueryHistory.user_id == user.id,
QueryHistory.is_favorite == True,
).order_by(desc(QueryHistory.executed_at))
)
hs = rows.scalars().all()
return [
{"id": h.id, "question": h.question, "sql": h.generated_sql,
"description": h.description, "executed_at": h.executed_at}
for h in hs
]
@router.get("/shared")
async def list_shared(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""팀 공유 쿼리 (모든 테넌트 사용자)."""
rows = await db.execute(
select(QueryHistory, User.name.label("owner")).join(
User, QueryHistory.user_id == User.id
).where(
QueryHistory.is_shared == True,
).order_by(desc(QueryHistory.executed_at)).limit(100)
)
return [
{"id": r.QueryHistory.id, "question": r.QueryHistory.question,
"sql": r.QueryHistory.generated_sql, "description": r.QueryHistory.description,
"owner": r.owner, "executed_at": r.QueryHistory.executed_at}
for r in rows.all()
]
@router.post("/save")
async def save_query(
req: SaveQueryRequest,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
h = QueryHistory(
user_id=user.id,
question=req.question,
generated_sql=req.sql,
description=req.description,
row_count=0,
is_favorite=True,
executed_at=datetime.utcnow(),
)
db.add(h)
await db.commit()
await db.refresh(h)
return {"ok": True, "id": h.id}
@router.post("/{history_id}/favorite")
async def toggle_favorite(
history_id: int,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
row = await db.execute(
select(QueryHistory).where(
QueryHistory.id == history_id,
QueryHistory.user_id == user.id,
)
)
h = row.scalar_one_or_none()
if not h:
raise HTTPException(404)
h.is_favorite = not h.is_favorite
await db.commit()
return {"ok": True, "is_favorite": h.is_favorite}
@router.post("/{history_id}/share")
async def toggle_share(
history_id: int,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
row = await db.execute(
select(QueryHistory).where(
QueryHistory.id == history_id,
QueryHistory.user_id == user.id,
)
)
h = row.scalar_one_or_none()
if not h:
raise HTTPException(404)
h.is_shared = not h.is_shared
await db.commit()
return {"ok": True, "is_shared": h.is_shared}
@router.delete("/{history_id}")
async def delete_history(
history_id: int,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
row = await db.execute(
select(QueryHistory).where(
QueryHistory.id == history_id,
QueryHistory.user_id == user.id,
)
)
h = row.scalar_one_or_none()
if not h:
raise HTTPException(404)
await db.delete(h)
await db.commit()
return {"ok": True}