- itsm/ -> workspace/guardia-itsm/ - manager/ -> workspace/guardia-manager/ - app/ -> workspace/guardia-messenger/ - manual/ -> workspace/guardia-docs/ workspace/zioinfo-web/ unchanged. git mv preserves full commit history. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
252 lines
8.6 KiB
Python
252 lines
8.6 KiB
Python
"""기관(사이트) + 담당자 등록·관리 엔드포인트."""
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy import select, func
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from core.auth import get_current_user
|
|
from database import get_db
|
|
from middleware.license_guard import check_institution_limit
|
|
from models import (
|
|
Institution, InstitutionCreate, InstitutionOut, InstitutionUpdate,
|
|
InstContact, InstContactCreate, InstContactOut, InstContactUpdate,
|
|
Server, User, UserRole,
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/institutions", tags=["institutions"])
|
|
|
|
|
|
def _require_admin_or_pm(user: User):
|
|
if user.role not in (UserRole.ADMIN, UserRole.PM):
|
|
raise HTTPException(403, "ADMIN 또는 PM 권한이 필요합니다.")
|
|
|
|
|
|
# ── 기관 CRUD ──────────────────────────────────────────────────────────────────
|
|
|
|
@router.get("", response_model=List[InstitutionOut])
|
|
async def list_institutions(
|
|
keyword: Optional[str] = Query(None),
|
|
region: Optional[str] = Query(None),
|
|
is_active: Optional[bool] = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
q = select(Institution).order_by(Institution.inst_name)
|
|
if keyword:
|
|
q = q.where(Institution.inst_name.contains(keyword))
|
|
if region:
|
|
q = q.where(Institution.region == region)
|
|
if is_active is not None:
|
|
q = q.where(Institution.is_active == is_active)
|
|
result = await db.execute(q)
|
|
return result.scalars().all()
|
|
|
|
|
|
@router.get("/{inst_code}", response_model=InstitutionOut)
|
|
async def get_institution(
|
|
inst_code: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
_u: User = Depends(get_current_user),
|
|
):
|
|
r = await db.execute(select(Institution).where(Institution.inst_code == inst_code))
|
|
inst = r.scalars().first()
|
|
if not inst:
|
|
raise HTTPException(404, "기관을 찾을 수 없습니다.")
|
|
return inst
|
|
|
|
|
|
@router.post("", response_model=InstitutionOut, status_code=201)
|
|
async def create_institution(
|
|
payload: InstitutionCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
_: None = Depends(check_institution_limit),
|
|
):
|
|
_require_admin_or_pm(current_user)
|
|
# 중복 코드 확인
|
|
dup = await db.execute(
|
|
select(Institution).where(Institution.inst_code == payload.inst_code)
|
|
)
|
|
if dup.scalars().first():
|
|
raise HTTPException(409, f"기관 코드 '{payload.inst_code}'가 이미 존재합니다.")
|
|
inst = Institution(**payload.model_dump())
|
|
db.add(inst)
|
|
await db.commit()
|
|
await db.refresh(inst)
|
|
return inst
|
|
|
|
|
|
@router.patch("/{inst_code}", response_model=InstitutionOut)
|
|
async def update_institution(
|
|
inst_code: str,
|
|
payload: InstitutionUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_require_admin_or_pm(current_user)
|
|
r = await db.execute(select(Institution).where(Institution.inst_code == inst_code))
|
|
inst = r.scalars().first()
|
|
if not inst:
|
|
raise HTTPException(404, "기관을 찾을 수 없습니다.")
|
|
for k, v in payload.model_dump(exclude_unset=True).items():
|
|
setattr(inst, k, v)
|
|
inst.updated_at = datetime.now()
|
|
await db.commit()
|
|
await db.refresh(inst)
|
|
return inst
|
|
|
|
|
|
@router.delete("/{inst_code}", status_code=204)
|
|
async def deactivate_institution(
|
|
inst_code: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
if current_user.role != UserRole.ADMIN:
|
|
raise HTTPException(403, "ADMIN 권한이 필요합니다.")
|
|
r = await db.execute(select(Institution).where(Institution.inst_code == inst_code))
|
|
inst = r.scalars().first()
|
|
if not inst:
|
|
raise HTTPException(404, "기관을 찾을 수 없습니다.")
|
|
inst.is_active = False
|
|
inst.updated_at = datetime.now()
|
|
await db.commit()
|
|
|
|
|
|
# ── 담당자 CRUD ────────────────────────────────────────────────────────────────
|
|
|
|
async def _get_inst(db: AsyncSession, inst_code: str) -> Institution:
|
|
r = await db.execute(select(Institution).where(Institution.inst_code == inst_code))
|
|
inst = r.scalars().first()
|
|
if not inst:
|
|
raise HTTPException(404, "기관을 찾을 수 없습니다.")
|
|
return inst
|
|
|
|
|
|
@router.get("/{inst_code}/contacts", response_model=List[InstContactOut])
|
|
async def list_contacts(
|
|
inst_code: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
_u: User = Depends(get_current_user),
|
|
):
|
|
inst = await _get_inst(db, inst_code)
|
|
result = await db.execute(
|
|
select(InstContact)
|
|
.where(InstContact.inst_id == inst.id)
|
|
.order_by(InstContact.is_primary.desc(), InstContact.contact_name)
|
|
)
|
|
return result.scalars().all()
|
|
|
|
|
|
@router.post("/{inst_code}/contacts", response_model=InstContactOut, status_code=201)
|
|
async def add_contact(
|
|
inst_code: str,
|
|
payload: InstContactCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_require_admin_or_pm(current_user)
|
|
inst = await _get_inst(db, inst_code)
|
|
# 주 담당자로 설정 시 기존 주 담당자 해제
|
|
if payload.is_primary:
|
|
await db.execute(
|
|
select(InstContact).where(
|
|
InstContact.inst_id == inst.id,
|
|
InstContact.is_primary == True
|
|
)
|
|
)
|
|
prev_primary = (await db.execute(
|
|
select(InstContact).where(
|
|
InstContact.inst_id == inst.id,
|
|
InstContact.is_primary == True
|
|
)
|
|
)).scalars().first()
|
|
if prev_primary:
|
|
prev_primary.is_primary = False
|
|
contact = InstContact(inst_id=inst.id, **payload.model_dump())
|
|
db.add(contact)
|
|
await db.commit()
|
|
await db.refresh(contact)
|
|
return contact
|
|
|
|
|
|
@router.patch("/{inst_code}/contacts/{contact_id}", response_model=InstContactOut)
|
|
async def update_contact(
|
|
inst_code: str,
|
|
contact_id: int,
|
|
payload: InstContactUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_require_admin_or_pm(current_user)
|
|
inst = await _get_inst(db, inst_code)
|
|
r = await db.execute(
|
|
select(InstContact).where(
|
|
InstContact.id == contact_id,
|
|
InstContact.inst_id == inst.id
|
|
)
|
|
)
|
|
contact = r.scalars().first()
|
|
if not contact:
|
|
raise HTTPException(404, "담당자를 찾을 수 없습니다.")
|
|
# 주 담당자로 변경 시 기존 주 담당자 해제
|
|
if payload.is_primary:
|
|
prev = (await db.execute(
|
|
select(InstContact).where(
|
|
InstContact.inst_id == inst.id,
|
|
InstContact.is_primary == True,
|
|
InstContact.id != contact_id
|
|
)
|
|
)).scalars().first()
|
|
if prev:
|
|
prev.is_primary = False
|
|
for k, v in payload.model_dump(exclude_unset=True).items():
|
|
setattr(contact, k, v)
|
|
await db.commit()
|
|
await db.refresh(contact)
|
|
return contact
|
|
|
|
|
|
@router.delete("/{inst_code}/contacts/{contact_id}", status_code=204)
|
|
async def delete_contact(
|
|
inst_code: str,
|
|
contact_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
if current_user.role != UserRole.ADMIN:
|
|
raise HTTPException(403, "ADMIN 권한이 필요합니다.")
|
|
inst = await _get_inst(db, inst_code)
|
|
r = await db.execute(
|
|
select(InstContact).where(
|
|
InstContact.id == contact_id,
|
|
InstContact.inst_id == inst.id
|
|
)
|
|
)
|
|
contact = r.scalars().first()
|
|
if not contact:
|
|
raise HTTPException(404, "담당자를 찾을 수 없습니다.")
|
|
contact.is_active = False
|
|
await db.commit()
|
|
|
|
|
|
# ── 기관 서버 목록 (기존 cmdb 대체) ───────────────────────────────────────────
|
|
|
|
@router.get("/{inst_code}/servers")
|
|
async def list_inst_servers(
|
|
inst_code: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
_u: User = Depends(get_current_user),
|
|
):
|
|
from models import ServerOut
|
|
inst = await _get_inst(db, inst_code)
|
|
result = await db.execute(
|
|
select(Server)
|
|
.where(Server.inst_id == inst.id)
|
|
.order_by(Server.server_role, Server.server_name)
|
|
)
|
|
servers = result.scalars().all()
|
|
return [ServerOut.model_validate(s) for s in servers]
|