utp_service/backend/app/routers/objects.py
2026-04-07 00:00:50 +03:00

138 lines
4.4 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.dependencies import get_current_user, get_db
from app.models.device import Device
from app.models.object import Object
from app.models.tag import Tag
from app.models.user import User
from app.schemas.object import ObjectCreate, ObjectRead, ObjectUpdate, SyncResult
from app.services import crypto
from app.services.object_db import sync_from_object_db
router = APIRouter(prefix="/api/v1/objects", tags=["objects"])
def _apply_credentials(obj: Object, db_password: str | None, ssh_password: str | None) -> None:
if db_password is not None:
obj.db_pass_enc = crypto.encrypt(db_password)
if ssh_password is not None:
obj.ssh_pass_enc = crypto.encrypt(ssh_password)
async def _load_object(db: AsyncSession, obj_id: int) -> Object:
result = await db.execute(
select(Object).options(selectinload(Object.tags)).where(Object.id == obj_id)
)
obj = result.scalar_one_or_none()
if obj is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Object not found")
return obj
@router.get("", response_model=list[ObjectRead])
async def list_objects(
is_active: bool | None = None,
tag_id: int | None = None,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
query = select(Object).options(selectinload(Object.tags))
if is_active is not None:
query = query.where(Object.is_active == is_active)
if tag_id is not None:
query = query.where(Object.tags.any(Tag.id == tag_id))
query = query.order_by(Object.name)
result = await db.execute(query)
return result.scalars().all()
@router.post("", response_model=ObjectRead, status_code=status.HTTP_201_CREATED)
async def create_object(
body: ObjectCreate,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
data = body.model_dump(exclude={"db_password", "ssh_password", "tag_ids"})
obj = Object(**data)
_apply_credentials(obj, body.db_password, body.ssh_password)
if body.tag_ids:
tags_result = await db.execute(select(Tag).where(Tag.id.in_(body.tag_ids)))
obj.tags = list(tags_result.scalars().all())
db.add(obj)
await db.flush()
await db.refresh(obj, ["tags"])
return obj
@router.get("/{obj_id}", response_model=ObjectRead)
async def get_object(
obj_id: int,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
return await _load_object(db, obj_id)
@router.patch("/{obj_id}", response_model=ObjectRead)
async def update_object(
obj_id: int,
body: ObjectUpdate,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
obj = await _load_object(db, obj_id)
data = body.model_dump(exclude_none=True, exclude={"db_password", "ssh_password", "tag_ids"})
for field, value in data.items():
setattr(obj, field, value)
_apply_credentials(obj, body.db_password, body.ssh_password)
if body.tag_ids is not None:
tags_result = await db.execute(select(Tag).where(Tag.id.in_(body.tag_ids)))
obj.tags = list(tags_result.scalars().all())
await db.flush()
await db.refresh(obj, ["tags"])
return obj
@router.delete("/{obj_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_object(
obj_id: int,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
obj = await _load_object(db, obj_id)
# Check for devices (RESTRICT constraint would catch this, but give a clear message)
count_result = await db.execute(
select(Device.id).where(Device.object_id == obj_id).limit(1)
)
if count_result.scalar_one_or_none() is not None:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Cannot delete object with existing devices. Remove all devices first.",
)
await db.delete(obj)
@router.post("/{obj_id}/sync", response_model=SyncResult)
async def sync_devices(
obj_id: int,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
obj = await _load_object(db, obj_id)
result = await sync_from_object_db(db, obj)
return SyncResult(
created=result.created,
updated=result.updated,
skipped=result.skipped,
errors=result.errors,
)