guardia-itsm/routers/idp_catalog.py
2026-06-03 08:04:03 +09:00

125 lines
5.2 KiB
Python

"""IDP 소프트웨어 카탈로그 — Backstage-style 서비스 등록·조회"""
from __future__ import annotations
import json, logging
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
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, IDPComponent
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/idp/catalog", tags=["IDP 카탈로그"])
class ComponentCreate(BaseModel):
name: str; display_name: str = ""; component_type: str = "service"
description: str = ""; language: str = ""; framework: str = ""
gitea_repo: str = ""; ci_job: str = ""; docs_url: str = ""
owner_team: str = ""; tags: list = []; lifecycle: str = "production"
@router.get("")
async def list_components(
q: Optional[str] = None, type_: Optional[str] = Query(None, alias="type"),
limit: int = Query(50, le=200),
db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user),
):
stmt = select(IDPComponent).order_by(IDPComponent.name).limit(limit)
if q:
stmt = stmt.where(IDPComponent.name.contains(q) | IDPComponent.description.contains(q))
if type_:
stmt = stmt.where(IDPComponent.component_type == type_)
rows = await db.execute(stmt)
comps = rows.scalars().all()
return [{"id":c.id,"name":c.name,"display_name":c.display_name,"component_type":c.component_type,
"language":c.language,"lifecycle":c.lifecycle,"owner_team":c.owner_team}
for c in comps]
@router.post("", status_code=201)
async def register_component(body: ComponentCreate, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
comp = IDPComponent(
**{k: v for k, v in body.model_dump().items() if k != "tags"},
tags=json.dumps(body.tags),
registered_by=user.id, created_at=datetime.utcnow()
)
db.add(comp); await db.commit(); await db.refresh(comp)
return {"id": comp.id}
@router.get("/{comp_id}")
async def get_component(comp_id: int, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
row = await db.execute(select(IDPComponent).where(IDPComponent.id == comp_id))
comp = row.scalar_one_or_none()
if not comp: raise HTTPException(404)
return {
"id": comp.id, "name": comp.name, "display_name": comp.display_name,
"component_type": comp.component_type, "description": comp.description,
"language": comp.language, "framework": comp.framework,
"gitea_repo": comp.gitea_repo, "ci_job": comp.ci_job,
"docs_url": comp.docs_url, "owner_team": comp.owner_team,
"tags": json.loads(comp.tags or "[]"), "lifecycle": comp.lifecycle,
"created_at": comp.created_at,
}
@router.put("/{comp_id}")
async def update_component(comp_id: int, body: ComponentCreate, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
row = await db.execute(select(IDPComponent).where(IDPComponent.id == comp_id))
comp = row.scalar_one_or_none()
if not comp: raise HTTPException(404)
for k, v in body.model_dump().items():
if k == "tags":
comp.tags = json.dumps(v)
else:
setattr(comp, k, v)
await db.commit(); return {"ok": True}
@router.delete("/{comp_id}")
async def delete_component(comp_id: int, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
row = await db.execute(select(IDPComponent).where(IDPComponent.id == comp_id))
comp = row.scalar_one_or_none()
if not comp: raise HTTPException(404)
await db.delete(comp); await db.commit(); return {"ok": True}
@router.get("/{comp_id}/deps")
async def get_deps(comp_id: int, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
row = await db.execute(select(IDPComponent).where(IDPComponent.id == comp_id))
comp = row.scalar_one_or_none()
if not comp: raise HTTPException(404)
deps = json.loads(comp.dependencies or "[]")
return {"component": comp.name, "dependencies": deps}
@router.get("/{comp_id}/health")
async def get_health(comp_id: int, db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
row = await db.execute(select(IDPComponent).where(IDPComponent.id == comp_id))
comp = row.scalar_one_or_none()
if not comp: raise HTTPException(404)
return {"component": comp.name, "status": "UNKNOWN", "last_deploy": comp.created_at,
"note": "CI/CD 연동 시 실시간 상태 제공"}
@router.get("/search")
async def search(q: str = "", db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user)):
stmt = select(IDPComponent).where(
IDPComponent.name.contains(q) | IDPComponent.description.contains(q) |
IDPComponent.tags.contains(q)
).limit(20)
rows = await db.execute(stmt)
return [{"id":c.id,"name":c.name,"type":c.component_type,"language":c.language}
for c in rows.scalars().all()]