First commit
This commit is contained in:
189
src/routers/gates.py
Normal file
189
src/routers/gates.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.auth import decrypt_secret
|
||||
from core.database import ApiCredential, GateAccessLog, GateDB, Keypass, get_db
|
||||
from core.dependencies import require_admin, require_manager, require_keypass
|
||||
from models import Credential, Gate as GateModel, Status
|
||||
from core.schemas import GateCreate, GateResponse
|
||||
from services.gates import call_open_gate
|
||||
|
||||
router = APIRouter(tags=["gates"])
|
||||
|
||||
|
||||
# ── Admin: gate CRUD ──────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/api/admin/gates", response_model=list[GateResponse])
|
||||
async def list_gates_admin(
|
||||
db: Session = Depends(get_db), _: dict = Depends(require_manager)
|
||||
):
|
||||
return db.query(GateDB).order_by(GateDB.id).all()
|
||||
|
||||
|
||||
@router.post("/api/admin/gates", response_model=GateResponse, status_code=201)
|
||||
async def create_gate(
|
||||
req: GateCreate,
|
||||
db: Session = Depends(get_db),
|
||||
_: dict = Depends(require_admin),
|
||||
):
|
||||
if req.gate_type not in ("car", "pedestrian"):
|
||||
raise HTTPException(400, "gate_type must be 'car' or 'pedestrian'")
|
||||
gate = GateDB(**req.model_dump())
|
||||
db.add(gate)
|
||||
db.commit()
|
||||
db.refresh(gate)
|
||||
return gate
|
||||
|
||||
|
||||
@router.put("/api/admin/gates/{gate_id}", response_model=GateResponse)
|
||||
async def update_gate(
|
||||
gate_id: int,
|
||||
req: GateCreate,
|
||||
db: Session = Depends(get_db),
|
||||
_: dict = Depends(require_admin),
|
||||
):
|
||||
gate: Optional[GateDB] = db.query(GateDB).filter(GateDB.id == gate_id).first()
|
||||
if not gate:
|
||||
raise HTTPException(404, "Gate not found")
|
||||
for k, v in req.model_dump().items():
|
||||
setattr(gate, k, v)
|
||||
db.commit()
|
||||
db.refresh(gate)
|
||||
return gate
|
||||
|
||||
|
||||
@router.delete("/api/admin/gates/{gate_id}", status_code=204)
|
||||
async def delete_gate(
|
||||
gate_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_: dict = Depends(require_admin),
|
||||
):
|
||||
gate: Optional[GateDB] = db.query(GateDB).filter(GateDB.id == gate_id).first()
|
||||
if not gate:
|
||||
raise HTTPException(404, "Gate not found")
|
||||
db.delete(gate)
|
||||
db.commit()
|
||||
|
||||
|
||||
@router.post("/api/admin/gates/{gate_id}/open")
|
||||
async def admin_open_gate(
|
||||
gate_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
caller: dict = Depends(require_manager),
|
||||
):
|
||||
gate_db: Optional[GateDB] = db.query(GateDB).filter(GateDB.id == gate_id).first()
|
||||
if not gate_db:
|
||||
raise HTTPException(404, "Gate not found")
|
||||
if gate_db.status != "enabled":
|
||||
raise HTTPException(409, "Gate is disabled")
|
||||
|
||||
cred_db: Optional[ApiCredential] = db.query(ApiCredential).first()
|
||||
if not cred_db:
|
||||
raise HTTPException(503, "AVConnect credentials not configured")
|
||||
|
||||
credential = Credential(
|
||||
username=cred_db.username,
|
||||
password=decrypt_secret(cred_db.password_enc),
|
||||
)
|
||||
credential.sessionid = cred_db.session_id
|
||||
|
||||
gate = GateModel(id=gate_db.avconnect_macro_id, name=gate_db.name, status=Status.ENABLED)
|
||||
ip = request.headers.get("X-Forwarded-For", request.client.host if request.client else None)
|
||||
ua = request.headers.get("User-Agent")
|
||||
|
||||
success, error_msg, new_sid = call_open_gate(gate, credential)
|
||||
|
||||
db.add(GateAccessLog(
|
||||
timestamp=datetime.utcnow(),
|
||||
keypass_id=0,
|
||||
keypass_code=f"[{caller['sub']}]",
|
||||
gate_id=gate_db.id,
|
||||
gate_name=gate_db.name,
|
||||
ip_address=ip,
|
||||
user_agent=ua,
|
||||
success=success,
|
||||
error=error_msg,
|
||||
))
|
||||
|
||||
if new_sid and new_sid != cred_db.session_id:
|
||||
cred_db.session_id = new_sid
|
||||
db.commit()
|
||||
|
||||
if not success:
|
||||
raise HTTPException(502, error_msg or "Gate operation failed")
|
||||
|
||||
return {"success": True, "gate": gate_db.name}
|
||||
|
||||
|
||||
# ── User-facing gate routes ───────────────────────────────────────────────────
|
||||
|
||||
@router.get("/api/gates", response_model=list[GateResponse])
|
||||
async def list_gates(
|
||||
db: Session = Depends(get_db), _kp: Keypass = Depends(require_keypass)
|
||||
):
|
||||
allowed = json.loads(_kp.allowed_gates) if _kp.allowed_gates else None
|
||||
q = db.query(GateDB).filter(GateDB.status == "enabled")
|
||||
if allowed is not None:
|
||||
q = q.filter(GateDB.id.in_(allowed))
|
||||
return q.order_by(GateDB.id).all()
|
||||
|
||||
|
||||
@router.post("/api/gates/{gate_id}/open")
|
||||
async def open_gate(
|
||||
gate_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
_kp: Keypass = Depends(require_keypass),
|
||||
):
|
||||
gate_db: Optional[GateDB] = db.query(GateDB).filter(
|
||||
GateDB.id == gate_id, GateDB.status == "enabled"
|
||||
).first()
|
||||
if not gate_db:
|
||||
raise HTTPException(404, "Gate not found or disabled")
|
||||
|
||||
cred_db: Optional[ApiCredential] = db.query(ApiCredential).first()
|
||||
if not cred_db:
|
||||
raise HTTPException(503, "AVConnect credentials not configured")
|
||||
|
||||
credential = Credential(
|
||||
username=cred_db.username,
|
||||
password=decrypt_secret(cred_db.password_enc),
|
||||
)
|
||||
credential.sessionid = cred_db.session_id
|
||||
|
||||
gate_status = Status.ENABLED if gate_db.status == "enabled" else Status.DISABLED
|
||||
gate = GateModel(id=gate_db.avconnect_macro_id, name=gate_db.name, status=gate_status)
|
||||
ip = request.headers.get("X-Forwarded-For", request.client.host if request.client else None)
|
||||
ua = request.headers.get("User-Agent")
|
||||
|
||||
allowed = json.loads(_kp.allowed_gates) if _kp.allowed_gates else None
|
||||
if allowed is not None and gate_id not in allowed:
|
||||
raise HTTPException(403, "This keypass does not have access to this gate")
|
||||
|
||||
success, error_msg, new_sid = call_open_gate(gate, credential)
|
||||
|
||||
db.add(GateAccessLog(
|
||||
timestamp=datetime.utcnow(),
|
||||
keypass_id=_kp.id,
|
||||
keypass_code=_kp.code,
|
||||
gate_id=gate_db.id,
|
||||
gate_name=gate_db.name,
|
||||
ip_address=ip,
|
||||
user_agent=ua,
|
||||
success=success,
|
||||
error=error_msg,
|
||||
))
|
||||
|
||||
if new_sid and new_sid != cred_db.session_id:
|
||||
cred_db.session_id = new_sid
|
||||
db.commit()
|
||||
|
||||
if not success:
|
||||
raise HTTPException(502, error_msg or "Gate operation failed")
|
||||
|
||||
return {"success": True, "gate": gate_db.name}
|
||||
Reference in New Issue
Block a user