restructure: move backend into backend/
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Device management — a logical view spanning three tables.
|
||||
|
||||
A "device" is a client identified by its MAC address, which is used verbatim as
|
||||
the RADIUS ``username``. One device touches three tables:
|
||||
|
||||
radcheck username = MAC, Cleartext-Password := MAC (auth)
|
||||
radusergroup username = MAC, groupname = <group> (VLAN/group membership)
|
||||
customers username = MAC, mac_address = MAC, status (billing/metadata)
|
||||
|
||||
This router hides that fan-out behind mac_address + group + status.
|
||||
"""
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import delete, func, select, update
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database import get_db
|
||||
from ..errors import APIError
|
||||
from ..models import Customer, RadCheck, RadGroupReply, RadUserGroup
|
||||
from ..pagination import Page, PageParams
|
||||
from ..schemas import DeviceCreate, DeviceEdit, DeviceOut
|
||||
|
||||
router = APIRouter(prefix="/device", tags=["device"])
|
||||
|
||||
|
||||
def _group_exists(db: Session, group: str) -> bool:
|
||||
stmt = select(RadGroupReply.id).where(RadGroupReply.groupname == group).limit(1)
|
||||
return db.execute(stmt).first() is not None
|
||||
|
||||
|
||||
def _device_exists(db: Session, mac: str) -> bool:
|
||||
stmt = select(Customer.id).where(Customer.username == mac).limit(1)
|
||||
return db.execute(stmt).first() is not None
|
||||
|
||||
|
||||
@router.get("/", response_model=Page[DeviceOut])
|
||||
def list_devices(page: PageParams = Depends(), db: Session = Depends(get_db)):
|
||||
"""List devices — MAC, group and status, joined from customers + radusergroup."""
|
||||
base = select(
|
||||
Customer.mac_address, RadUserGroup.groupname, Customer.status,
|
||||
Customer.name, Customer.phone, Customer.device_alias,
|
||||
).outerjoin(RadUserGroup, RadUserGroup.username == Customer.username)
|
||||
total = db.execute(select(func.count()).select_from(Customer)).scalar_one()
|
||||
rows = db.execute(base.order_by(Customer.id.desc()).limit(page.limit).offset(page.offset)).all()
|
||||
items = [
|
||||
DeviceOut(mac_address=mac, group=gn, status=st, name=nm, phone=ph, alias=al)
|
||||
for mac, gn, st, nm, ph, al in rows
|
||||
]
|
||||
return Page(total=int(total), limit=page.limit, offset=page.offset, items=items)
|
||||
|
||||
|
||||
@router.get("/{mac_address}", response_model=DeviceOut)
|
||||
def get_device(mac_address: str, db: Session = Depends(get_db)):
|
||||
mac = mac_address.strip().upper().replace(":", "-")
|
||||
stmt = (
|
||||
select(
|
||||
Customer.mac_address, RadUserGroup.groupname, Customer.status,
|
||||
Customer.name, Customer.phone, Customer.device_alias,
|
||||
)
|
||||
.outerjoin(RadUserGroup, RadUserGroup.username == Customer.username)
|
||||
.where(Customer.username == mac)
|
||||
)
|
||||
row = db.execute(stmt).first()
|
||||
if row is None:
|
||||
raise APIError(status_code=404, detail=f"Device '{mac}' not found")
|
||||
return DeviceOut(
|
||||
mac_address=row[0], group=row[1], status=row[2],
|
||||
name=row[3], phone=row[4], alias=row[5],
|
||||
)
|
||||
|
||||
|
||||
@router.post("/add", response_model=DeviceOut, status_code=201)
|
||||
def add_device(payload: DeviceCreate, db: Session = Depends(get_db)):
|
||||
"""Register a device: create its radcheck, radusergroup and customer rows."""
|
||||
mac = payload.mac_address
|
||||
|
||||
if not _group_exists(db, payload.group):
|
||||
raise APIError(status_code=400, detail=f"Group '{payload.group}' not found — create it first")
|
||||
if _device_exists(db, mac):
|
||||
raise APIError(status_code=409, detail=f"Device '{mac}' already exists")
|
||||
|
||||
db.add(RadCheck(username=mac, attribute="Cleartext-Password", op=":=", value=mac))
|
||||
db.add(RadUserGroup(username=mac, groupname=payload.group, priority=1))
|
||||
db.add(Customer(
|
||||
username=mac, mac_address=mac, status="paid",
|
||||
name=payload.name, phone=payload.phone, device_alias=payload.alias,
|
||||
))
|
||||
db.commit()
|
||||
return DeviceOut(
|
||||
mac_address=mac, group=payload.group, status="paid",
|
||||
name=payload.name, phone=payload.phone, alias=payload.alias,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/edit", response_model=DeviceOut)
|
||||
def edit_device(payload: DeviceEdit, db: Session = Depends(get_db)):
|
||||
"""Edit a device — any subset of group, status, name, phone, alias."""
|
||||
mac = payload.mac_address
|
||||
customer = db.execute(select(Customer).where(Customer.username == mac)).scalar_one_or_none()
|
||||
if customer is None:
|
||||
raise APIError(status_code=404, detail=f"Device '{mac}' not found")
|
||||
|
||||
if payload.group is not None:
|
||||
if not _group_exists(db, payload.group):
|
||||
raise APIError(status_code=400, detail=f"Group '{payload.group}' not found")
|
||||
db.execute(
|
||||
update(RadUserGroup).where(RadUserGroup.username == mac).values(groupname=payload.group)
|
||||
)
|
||||
|
||||
if payload.status is not None:
|
||||
customer.status = payload.status
|
||||
if payload.name is not None:
|
||||
customer.name = payload.name
|
||||
if payload.phone is not None:
|
||||
customer.phone = payload.phone
|
||||
if payload.alias is not None:
|
||||
customer.device_alias = payload.alias
|
||||
|
||||
db.commit()
|
||||
|
||||
group = db.execute(
|
||||
select(RadUserGroup.groupname).where(RadUserGroup.username == mac).limit(1)
|
||||
).scalar_one_or_none()
|
||||
return DeviceOut(
|
||||
mac_address=mac, group=group, status=customer.status,
|
||||
name=customer.name, phone=customer.phone, alias=customer.device_alias,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{mac_address}", status_code=204)
|
||||
def delete_device(mac_address: str, db: Session = Depends(get_db)):
|
||||
"""Remove a device from radcheck, radusergroup and customers."""
|
||||
mac = mac_address.strip().upper().replace(":", "-")
|
||||
if not _device_exists(db, mac):
|
||||
raise APIError(status_code=404, detail=f"Device '{mac}' not found")
|
||||
|
||||
db.execute(delete(RadCheck).where(RadCheck.username == mac))
|
||||
db.execute(delete(RadUserGroup).where(RadUserGroup.username == mac))
|
||||
db.execute(delete(Customer).where(Customer.username == mac))
|
||||
db.commit()
|
||||
Reference in New Issue
Block a user