import ipaddress import re from typing import Optional, List from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, field_validator from sqlalchemy import nullsfirst from sqlalchemy.orm import Session from database import get_db import models router = APIRouter() _COLOR_RE = re.compile(r"^#[0-9a-fA-F]{6}$") class VlanCreate(BaseModel): vlan_id: Optional[int] = None name: str cidr: Optional[str] = "" color: str = "#4A90D9" @field_validator("vlan_id") @classmethod def _vlan_id(cls, v: Optional[int]) -> Optional[int]: if v is not None and not (1 <= v <= 4094): raise ValueError("vlan_id must be between 1 and 4094") return v @field_validator("name") @classmethod def _name(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("name cannot be empty") if len(v) > 100: raise ValueError("name too long (max 100 characters)") return v @field_validator("cidr") @classmethod def _cidr(cls, v: Optional[str]) -> Optional[str]: if v: try: ipaddress.ip_network(v, strict=False) except ValueError: raise ValueError(f"Invalid CIDR notation: {v!r}") return v @field_validator("color") @classmethod def _color(cls, v: str) -> str: if not _COLOR_RE.match(v): raise ValueError("color must be a 6-digit hex color (e.g. #4A90D9)") return v class VlanOut(VlanCreate): id: int model_config = {"from_attributes": True} @router.get("/", response_model=List[VlanOut]) def list_vlans(db: Session = Depends(get_db)): return db.query(models.Vlan).order_by(nullsfirst(models.Vlan.vlan_id)).all() @router.post("/", response_model=VlanOut) def create_vlan(vlan: VlanCreate, db: Session = Depends(get_db)): if vlan.vlan_id is not None: existing = db.query(models.Vlan).filter(models.Vlan.vlan_id == vlan.vlan_id).first() if existing: raise HTTPException(status_code=400, detail=f"VLAN {vlan.vlan_id} existe déjà") db_vlan = models.Vlan(**vlan.model_dump()) db.add(db_vlan) db.commit() db.refresh(db_vlan) return db_vlan @router.put("/{vlan_pk}", response_model=VlanOut) def update_vlan(vlan_pk: int, vlan: VlanCreate, db: Session = Depends(get_db)): db_vlan = db.query(models.Vlan).filter(models.Vlan.id == vlan_pk).first() if not db_vlan: raise HTTPException(status_code=404, detail="VLAN introuvable") for k, v in vlan.model_dump().items(): setattr(db_vlan, k, v) db.commit() db.refresh(db_vlan) return db_vlan @router.delete("/{vlan_pk}") def delete_vlan(vlan_pk: int, db: Session = Depends(get_db)): db_vlan = db.query(models.Vlan).filter(models.Vlan.id == vlan_pk).first() if not db_vlan: raise HTTPException(status_code=404, detail="VLAN introuvable") db.delete(db_vlan) db.commit() return {"ok": True}