125 lines
5.2 KiB
Python
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()]
|