first ruggedy draft
This commit is contained in:
188
apps/api/mousehold_api/routers/taxonomy.py
Normal file
188
apps/api/mousehold_api/routers/taxonomy.py
Normal file
@@ -0,0 +1,188 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import models
|
||||
from ..db import get_session
|
||||
from ..schemas import (
|
||||
CategoryCreate,
|
||||
CategoryRead,
|
||||
CategoryUpdate,
|
||||
TagCreate,
|
||||
TagRead,
|
||||
TagUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["taxonomy"])
|
||||
|
||||
|
||||
def _require_household(session: Session, household_id: str) -> None:
|
||||
if not session.get(models.Household, household_id):
|
||||
raise HTTPException(status_code=404, detail="Household not found")
|
||||
|
||||
|
||||
def _category_path(
|
||||
session: Session,
|
||||
payload: CategoryCreate | CategoryUpdate,
|
||||
category: models.Category | None = None,
|
||||
) -> str:
|
||||
if payload.path:
|
||||
return payload.path
|
||||
name = payload.name if payload.name is not None else category.name if category else ""
|
||||
parent_id = payload.parent_id if payload.parent_id is not None else category.parent_id if category else None
|
||||
if parent_id:
|
||||
parent = session.get(models.Category, parent_id)
|
||||
if not parent:
|
||||
raise HTTPException(status_code=404, detail="Parent category not found")
|
||||
return f"{parent.path}/{name}"
|
||||
return name
|
||||
|
||||
|
||||
@router.post("/categories", response_model=CategoryRead)
|
||||
def create_category(
|
||||
payload: CategoryCreate,
|
||||
session: Session = Depends(get_session),
|
||||
) -> models.Category:
|
||||
_require_household(session, payload.household_id)
|
||||
if payload.parent_id:
|
||||
parent = session.get(models.Category, payload.parent_id)
|
||||
if not parent or parent.household_id != payload.household_id:
|
||||
raise HTTPException(status_code=404, detail="Parent category not found")
|
||||
category = models.Category(
|
||||
household_id=payload.household_id,
|
||||
parent_id=payload.parent_id,
|
||||
name=payload.name,
|
||||
path=_category_path(session, payload),
|
||||
is_active=payload.is_active,
|
||||
)
|
||||
session.add(category)
|
||||
session.commit()
|
||||
session.refresh(category)
|
||||
return category
|
||||
|
||||
|
||||
@router.get("/categories", response_model=list[CategoryRead])
|
||||
def list_categories(
|
||||
household_id: str,
|
||||
include_inactive: bool = False,
|
||||
session: Session = Depends(get_session),
|
||||
) -> list[models.Category]:
|
||||
query = (
|
||||
select(models.Category)
|
||||
.where(models.Category.household_id == household_id)
|
||||
.order_by(models.Category.path)
|
||||
)
|
||||
if not include_inactive:
|
||||
query = query.where(models.Category.is_active.is_(True))
|
||||
return list(session.scalars(query).all())
|
||||
|
||||
|
||||
@router.patch("/categories/{category_id}", response_model=CategoryRead)
|
||||
def update_category(
|
||||
category_id: str,
|
||||
payload: CategoryUpdate,
|
||||
session: Session = Depends(get_session),
|
||||
) -> models.Category:
|
||||
category = session.get(models.Category, category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Category not found")
|
||||
if payload.parent_id:
|
||||
parent = session.get(models.Category, payload.parent_id)
|
||||
if not parent or parent.household_id != category.household_id:
|
||||
raise HTTPException(status_code=404, detail="Parent category not found")
|
||||
for field, value in payload.model_dump(exclude_unset=True, exclude={"path"}).items():
|
||||
setattr(category, field, value)
|
||||
if {"name", "parent_id", "path"} & payload.model_fields_set:
|
||||
category.path = _category_path(session, payload, category)
|
||||
session.add(category)
|
||||
session.commit()
|
||||
session.refresh(category)
|
||||
return category
|
||||
|
||||
|
||||
@router.delete("/categories/{category_id}")
|
||||
def delete_category(
|
||||
category_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
) -> dict[str, bool]:
|
||||
category = session.get(models.Category, category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="Category not found")
|
||||
for event in session.scalars(
|
||||
select(models.FinancialEvent).where(models.FinancialEvent.category_id == category_id)
|
||||
):
|
||||
event.category_id = None
|
||||
for commitment in session.scalars(
|
||||
select(models.RecurringCommitment).where(models.RecurringCommitment.category_id == category_id)
|
||||
):
|
||||
commitment.category_id = None
|
||||
for budget_line in session.scalars(
|
||||
select(models.ProjectBudgetLine).where(models.ProjectBudgetLine.category_id == category_id)
|
||||
):
|
||||
budget_line.category_id = None
|
||||
for child in session.scalars(select(models.Category).where(models.Category.parent_id == category_id)):
|
||||
child.parent_id = None
|
||||
child.path = child.name
|
||||
session.delete(category)
|
||||
session.commit()
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
@router.post("/tags", response_model=TagRead)
|
||||
def create_tag(
|
||||
payload: TagCreate,
|
||||
session: Session = Depends(get_session),
|
||||
) -> models.Tag:
|
||||
_require_household(session, payload.household_id)
|
||||
tag = models.Tag(household_id=payload.household_id, name=payload.name)
|
||||
session.add(tag)
|
||||
session.commit()
|
||||
session.refresh(tag)
|
||||
return tag
|
||||
|
||||
|
||||
@router.get("/tags", response_model=list[TagRead])
|
||||
def list_tags(
|
||||
household_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
) -> list[models.Tag]:
|
||||
return list(
|
||||
session.scalars(
|
||||
select(models.Tag).where(models.Tag.household_id == household_id).order_by(models.Tag.name)
|
||||
).all()
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/tags/{tag_id}", response_model=TagRead)
|
||||
def update_tag(
|
||||
tag_id: str,
|
||||
payload: TagUpdate,
|
||||
session: Session = Depends(get_session),
|
||||
) -> models.Tag:
|
||||
tag = session.get(models.Tag, tag_id)
|
||||
if not tag:
|
||||
raise HTTPException(status_code=404, detail="Tag not found")
|
||||
for field, value in payload.model_dump(exclude_unset=True).items():
|
||||
setattr(tag, field, value)
|
||||
session.add(tag)
|
||||
session.commit()
|
||||
session.refresh(tag)
|
||||
return tag
|
||||
|
||||
|
||||
@router.delete("/tags/{tag_id}")
|
||||
def delete_tag(
|
||||
tag_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
) -> dict[str, bool]:
|
||||
tag = session.get(models.Tag, tag_id)
|
||||
if not tag:
|
||||
raise HTTPException(status_code=404, detail="Tag not found")
|
||||
session.query(models.EventTag).filter(models.EventTag.tag_id == tag_id).delete(
|
||||
synchronize_session=False
|
||||
)
|
||||
session.delete(tag)
|
||||
session.commit()
|
||||
return {"deleted": True}
|
||||
Reference in New Issue
Block a user