feat(crud): full CRUD endpoints for NetObject (POST/PUT/PATCH/DELETE)
CI / basic-check (push) Has been cancelled
CI / basic-check (push) Has been cancelled
This commit is contained in:
@@ -1,14 +1,25 @@
|
|||||||
|
import re
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.models import NetObjectModel
|
from app.models import NetObjectModel
|
||||||
from app.schemas import EntityResponse, ListResponse, NetObject
|
from app.schemas import EntityResponse, ListResponse, NetObject, NetObjectCreate, NetObjectUpdate
|
||||||
from app.services.inventory import _to_net_object
|
from app.services.inventory import _to_net_object
|
||||||
|
|
||||||
router = APIRouter(prefix='/objects', tags=['objects'])
|
router = APIRouter(prefix='/objects', tags=['objects'])
|
||||||
|
|
||||||
|
|
||||||
|
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('-')
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=ListResponse[NetObject])
|
@router.get('', response_model=ListResponse[NetObject])
|
||||||
def list_objects(db: Session = Depends(get_db)):
|
def list_objects(db: Session = Depends(get_db)):
|
||||||
rows = db.query(NetObjectModel).all()
|
rows = db.query(NetObjectModel).all()
|
||||||
@@ -21,3 +32,89 @@ def get_object(object_id: str, db: Session = Depends(get_db)):
|
|||||||
if not row:
|
if not row:
|
||||||
raise HTTPException(status_code=404, detail='Object not found')
|
raise HTTPException(status_code=404, detail='Object not found')
|
||||||
return EntityResponse(item=_to_net_object(row))
|
return EntityResponse(item=_to_net_object(row))
|
||||||
|
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|||||||
Reference in New Issue
Block a user