"""Router factories for the AVP-style tables (radcheck/radreply/radgroup*). They share the same shape — an id plus (username|groupname, attribute, op, value). Two factories cover the two owner shapes: user-owned (radcheck, radreply) and group-owned (radgroupcheck, radgroupreply). """ from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from .. import crud from ..database import Base, get_db from ..pagination import Page, PageParams def build_user_attr_router(*, model, prefix, tag, out_schema, create_schema, update_schema) -> APIRouter: router = APIRouter(prefix=prefix, tags=[tag]) @router.get("", response_model=Page[out_schema]) def list_items( page: PageParams = Depends(), username: str | None = None, attribute: str | None = None, db: Session = Depends(get_db), ): filters = {"username": username, "attribute": attribute} total = crud.count_rows(db, model, filters) rows = crud.list_rows(db, model, limit=page.limit, offset=page.offset, filters=filters, order_by=model.id.asc()) return Page(total=total, limit=page.limit, offset=page.offset, items=rows) _register_item_routes(router, model, out_schema, create_schema, update_schema) return router def build_group_attr_router(*, model, prefix, tag, out_schema, create_schema, update_schema) -> APIRouter: router = APIRouter(prefix=prefix, tags=[tag]) @router.get("", response_model=Page[out_schema]) def list_items( page: PageParams = Depends(), groupname: str | None = None, attribute: str | None = None, db: Session = Depends(get_db), ): filters = {"groupname": groupname, "attribute": attribute} total = crud.count_rows(db, model, filters) rows = crud.list_rows(db, model, limit=page.limit, offset=page.offset, filters=filters, order_by=model.id.asc()) return Page(total=total, limit=page.limit, offset=page.offset, items=rows) _register_item_routes(router, model, out_schema, create_schema, update_schema) return router def _register_item_routes(router: APIRouter, model: type[Base], out_schema, create_schema, update_schema) -> None: @router.get("/{item_id}", response_model=out_schema) def get_item(item_id: int, db: Session = Depends(get_db)): return crud.get_row_or_404(db, model, item_id) @router.post("", response_model=out_schema, status_code=201) def create_item(payload: create_schema, db: Session = Depends(get_db)): return crud.create_row(db, model, payload.model_dump()) @router.put("/{item_id}", response_model=out_schema) def update_item(item_id: int, payload: update_schema, db: Session = Depends(get_db)): obj = crud.get_row_or_404(db, model, item_id) return crud.update_row(db, obj, payload.model_dump(exclude_unset=True)) @router.delete("/{item_id}", status_code=204) def delete_item(item_id: int, db: Session = Depends(get_db)): obj = crud.get_row_or_404(db, model, item_id) crud.delete_row(db, obj)