admin / Synapse-Cortex
publicSelf Hosted ITSM Tool with RBAC/Tenanting and MFA
Synapse-Cortex / Synapse-Cortexv2 / app / routers / assets.py
6239 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | import uuid from typing import Optional from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from .. import models, schemas from ..audit import log_audit from ..database import get_db from ..deps import get_session_user from ..utils import generate_asset_id router = APIRouter(prefix="/api/v1/assets", tags=["assets"]) @router.get("", response_model=list[schemas.AssetOut]) def list_assets( db: Session = Depends(get_db), user: Optional[models.User] = Depends(get_session_user), ): if not user: raise HTTPException(status.HTTP_401_UNAUTHORIZED) return ( db.query(models.Asset) .filter(models.Asset.tenant_id == user.tenant_id) .order_by(models.Asset.created_at.desc()) .all() ) @router.get("/{asset_id}", response_model=schemas.AssetOut) def get_asset( asset_id: uuid.UUID, db: Session = Depends(get_db), user: Optional[models.User] = Depends(get_session_user), ): if not user: raise HTTPException(status.HTTP_401_UNAUTHORIZED) asset = ( db.query(models.Asset) .filter(models.Asset.id == asset_id, models.Asset.tenant_id == user.tenant_id) .first() ) if not asset: raise HTTPException(status.HTTP_404_NOT_FOUND) return asset @router.post("", response_model=schemas.AssetOut, status_code=status.HTTP_201_CREATED) def create_asset( payload: schemas.AssetCreate, db: Session = Depends(get_db), user: Optional[models.User] = Depends(get_session_user), ): if not user: raise HTTPException(status.HTTP_401_UNAUTHORIZED) existing = ( db.query(models.Asset) .filter(models.Asset.tenant_id == user.tenant_id, models.Asset.mac_address == payload.mac_address) .first() ) if existing: raise HTTPException( status.HTTP_409_CONFLICT, detail="An asset with this MAC address already exists in this tenant", ) if payload.assigned_to_id: assignee = ( db.query(models.User) .filter(models.User.id == payload.assigned_to_id, models.User.tenant_id == user.tenant_id) .first() ) if not assignee: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Assignee not found in this tenant") asset_type = ( db.query(models.AssetTypeOption) .filter(models.AssetTypeOption.id == payload.asset_type_id, models.AssetTypeOption.tenant_id == user.tenant_id) .first() ) if not asset_type: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Asset type not found in this tenant") asset = models.Asset( tenant_id=user.tenant_id, asset_id=generate_asset_id(user.tenant_id, payload.mac_address), name=payload.name, asset_type_id=payload.asset_type_id, mac_address=payload.mac_address, ip_address=payload.ip_address, status=payload.status, location=payload.location, assigned_to_id=payload.assigned_to_id, ) db.add(asset) try: db.flush() except IntegrityError: db.rollback() raise HTTPException(status.HTTP_409_CONFLICT, detail="Asset conflict (duplicate MAC address or asset_id)") log_audit( db, tenant_id=user.tenant_id, user_id=user.id, action="asset_create", detail={"asset_id": asset.asset_id, "name": asset.name}, ) db.commit() db.refresh(asset) return asset @router.patch("/{asset_id}", response_model=schemas.AssetOut) def update_asset( asset_id: uuid.UUID, payload: schemas.AssetUpdate, db: Session = Depends(get_db), user: Optional[models.User] = Depends(get_session_user), ): if not user: raise HTTPException(status.HTTP_401_UNAUTHORIZED) asset = ( db.query(models.Asset) .filter(models.Asset.id == asset_id, models.Asset.tenant_id == user.tenant_id) .first() ) if not asset: raise HTTPException(status.HTTP_404_NOT_FOUND) if payload.assigned_to_id is not None: assignee = ( db.query(models.User) .filter(models.User.id == payload.assigned_to_id, models.User.tenant_id == user.tenant_id) .first() ) if not assignee: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Assignee not found in this tenant") if payload.asset_type_id is not None: asset_type = ( db.query(models.AssetTypeOption) .filter(models.AssetTypeOption.id == payload.asset_type_id, models.AssetTypeOption.tenant_id == user.tenant_id) .first() ) if not asset_type: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Asset type not found in this tenant") def _jsonable(value): if hasattr(value, "value"): # enum member return value.value if isinstance(value, uuid.UUID): return str(value) return value changes: dict = {} fields = payload.model_dump(exclude_unset=True, exclude={"software"}) for field, new_value in fields.items(): old_value = getattr(asset, field) if new_value != _jsonable(old_value): changes[field] = {"old": _jsonable(old_value), "new": _jsonable(new_value)} setattr(asset, field, new_value) if "software" in payload.model_fields_set: new_software = [s.model_dump() for s in payload.software] if payload.software else None if new_software != asset.software: changes["software"] = {"old": asset.software, "new": new_software} asset.software = new_software try: if changes: log_audit( db, tenant_id=user.tenant_id, user_id=user.id, action="asset_update", detail={"asset_id": asset.asset_id, "changes": changes}, ) db.commit() except IntegrityError: db.rollback() raise HTTPException(status.HTTP_409_CONFLICT, detail="An asset with this MAC address already exists in this tenant") db.refresh(asset) return asset |