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

165 lines
5.2 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.admin import SheetsSyncRequest
from app.schemas.object import ObjectCreate, ObjectRead, ObjectUpdate, SyncResult
from app.services import crypto
from app.services.object_db import sync_from_object_db
from app.services.sheets import sync_from_sheets
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,
)
@router.post("/{obj_id}/sync/sheets", response_model=SyncResult)
async def sync_devices_from_sheets(
obj_id: int,
body: SheetsSyncRequest,
db: AsyncSession = Depends(get_db),
_: User = Depends(get_current_user),
):
await _load_object(db, obj_id)
try:
result = await sync_from_sheets(
db,
object_id=obj_id,
spreadsheet_id=body.spreadsheet_id,
sheet_name=body.sheet_name,
)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
return SyncResult(
created=result.created,
updated=result.updated,
skipped=result.skipped,
errors=result.errors,
)