2026-08-06 16:09:13 +00:00
|
|
|
import re
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
2026-08-06 16:02:18 +00:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
|
from sqlalchemy.orm import Session
|
2026-08-04 18:55:54 +00:00
|
|
|
|
2026-08-06 16:02:18 +00:00
|
|
|
from app.database import get_db
|
|
|
|
|
from app.models import NetObjectModel
|
2026-08-06 16:09:13 +00:00
|
|
|
from app.schemas import EntityResponse, ListResponse, NetObject, NetObjectCreate, NetObjectUpdate
|
2026-08-06 16:02:18 +00:00
|
|
|
from app.services.inventory import _to_net_object
|
2026-08-04 18:55:54 +00:00
|
|
|
|
|
|
|
|
router = APIRouter(prefix='/objects', tags=['objects'])
|
|
|
|
|
|
|
|
|
|
|
2026-08-06 16:09:13 +00:00
|
|
|
def _slugify(name: str) -> str:
|
|
|
|
|
slug = name.lower().strip()
|
|
|
|
|
slug = re.sub(r'[^\w\s-]', '', slug)
|
|
|
|
|
slug = re.sub(r'[\s_-]+', '-', slug)
|
|
|
|
|
return slug.strip('-')
|
|
|
|
|
|
|
|
|
|
|
2026-08-04 18:55:54 +00:00
|
|
|
@router.get('', response_model=ListResponse[NetObject])
|
2026-08-06 16:02:18 +00:00
|
|
|
def list_objects(db: Session = Depends(get_db)):
|
|
|
|
|
rows = db.query(NetObjectModel).all()
|
|
|
|
|
return ListResponse(count=len(rows), items=[_to_net_object(r) for r in rows])
|
2026-08-04 18:55:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get('/{object_id}', response_model=EntityResponse[NetObject])
|
2026-08-06 16:02:18 +00:00
|
|
|
def get_object(object_id: str, db: Session = Depends(get_db)):
|
|
|
|
|
row = db.query(NetObjectModel).filter(NetObjectModel.id == object_id).first()
|
|
|
|
|
if not row:
|
|
|
|
|
raise HTTPException(status_code=404, detail='Object not found')
|
|
|
|
|
return EntityResponse(item=_to_net_object(row))
|
2026-08-06 16:09:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post('', response_model=EntityResponse[NetObject], status_code=201)
|
|
|
|
|
def create_object(body: NetObjectCreate, db: Session = Depends(get_db)):
|
|
|
|
|
if body.parent_id:
|
|
|
|
|
parent = db.query(NetObjectModel).filter(NetObjectModel.id == body.parent_id).first()
|
|
|
|
|
if not parent:
|
|
|
|
|
raise HTTPException(status_code=422, detail=f'parent_id {body.parent_id!r} not found')
|
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
|
obj = NetObjectModel(
|
|
|
|
|
id=f'obj_{uuid4().hex[:8]}',
|
|
|
|
|
name=body.name,
|
|
|
|
|
slug=_slugify(body.name),
|
|
|
|
|
type=body.type,
|
|
|
|
|
parent_id=body.parent_id,
|
|
|
|
|
status=body.status,
|
|
|
|
|
vendor=body.vendor,
|
|
|
|
|
model=body.model,
|
|
|
|
|
serial=body.serial,
|
|
|
|
|
notes=body.notes,
|
|
|
|
|
created_at=now,
|
|
|
|
|
updated_at=now,
|
|
|
|
|
)
|
|
|
|
|
db.add(obj)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(obj)
|
|
|
|
|
return EntityResponse(item=_to_net_object(obj))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put('/{object_id}', response_model=EntityResponse[NetObject])
|
|
|
|
|
def replace_object(object_id: str, body: NetObjectCreate, db: Session = Depends(get_db)):
|
|
|
|
|
row = db.query(NetObjectModel).filter(NetObjectModel.id == object_id).first()
|
|
|
|
|
if not row:
|
|
|
|
|
raise HTTPException(status_code=404, detail='Object not found')
|
|
|
|
|
if body.parent_id and body.parent_id != row.parent_id:
|
|
|
|
|
parent = db.query(NetObjectModel).filter(NetObjectModel.id == body.parent_id).first()
|
|
|
|
|
if not parent:
|
|
|
|
|
raise HTTPException(status_code=422, detail=f'parent_id {body.parent_id!r} not found')
|
|
|
|
|
row.name = body.name
|
|
|
|
|
row.slug = _slugify(body.name)
|
|
|
|
|
row.type = body.type
|
|
|
|
|
row.parent_id = body.parent_id
|
|
|
|
|
row.status = body.status
|
|
|
|
|
row.vendor = body.vendor
|
|
|
|
|
row.model = body.model
|
|
|
|
|
row.serial = body.serial
|
|
|
|
|
row.notes = body.notes
|
|
|
|
|
row.updated_at = datetime.now(timezone.utc)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(row)
|
|
|
|
|
return EntityResponse(item=_to_net_object(row))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.patch('/{object_id}', response_model=EntityResponse[NetObject])
|
|
|
|
|
def update_object(object_id: str, body: NetObjectUpdate, db: Session = Depends(get_db)):
|
|
|
|
|
row = db.query(NetObjectModel).filter(NetObjectModel.id == object_id).first()
|
|
|
|
|
if not row:
|
|
|
|
|
raise HTTPException(status_code=404, detail='Object not found')
|
|
|
|
|
data = body.model_dump(exclude_unset=True)
|
|
|
|
|
if 'parent_id' in data and data['parent_id']:
|
|
|
|
|
parent = db.query(NetObjectModel).filter(NetObjectModel.id == data['parent_id']).first()
|
|
|
|
|
if not parent:
|
|
|
|
|
raise HTTPException(status_code=422, detail=f'parent_id {data["parent_id"]!r} not found')
|
|
|
|
|
if 'name' in data:
|
|
|
|
|
row.slug = _slugify(data['name'])
|
|
|
|
|
for field, value in data.items():
|
|
|
|
|
setattr(row, field, value)
|
|
|
|
|
row.updated_at = datetime.now(timezone.utc)
|
|
|
|
|
db.commit()
|
|
|
|
|
db.refresh(row)
|
|
|
|
|
return EntityResponse(item=_to_net_object(row))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete('/{object_id}', status_code=204)
|
|
|
|
|
def delete_object(object_id: str, db: Session = Depends(get_db)):
|
|
|
|
|
row = db.query(NetObjectModel).filter(NetObjectModel.id == object_id).first()
|
|
|
|
|
if not row:
|
|
|
|
|
raise HTTPException(status_code=404, detail='Object not found')
|
|
|
|
|
children = db.query(NetObjectModel).filter(NetObjectModel.parent_id == object_id).count()
|
|
|
|
|
if children:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=409,
|
|
|
|
|
detail=f'Cannot delete: object has {children} child object(s). Remove or re-parent them first.',
|
|
|
|
|
)
|
|
|
|
|
db.delete(row)
|
|
|
|
|
db.commit()
|