feat(sysadmin): Part 3 — Remote Managers section in SiteDetailPage
Backend (manager_auth.py):
- register: upsert instead of 409 — if email exists, just add site access
- GET /api/manager/by-site/{site_id}: list managers linked to a site (admin auth)
- DELETE /api/manager/site-access: remove a manager's access to a site (admin auth)
Sysadmin API client:
- getManagersBySite, addManagerToSite, removeManagerSiteAccess
SiteDetailPage.jsx:
- Remote Managers section: lists all linked managers with email, name, active badge
- Add Manager modal: email + full name + password form (password ignored if account exists)
- Remove access button with optimistic removal from list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from jose import jwt, JWTError
|
||||
from passlib.context import CryptContext
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from config import settings
|
||||
from database import get_db
|
||||
from models.manager_account import ManagerAccount
|
||||
from models.manager_account import ManagerAccount, manager_site_access
|
||||
from models.site import Site
|
||||
from auth_utils import get_current_admin
|
||||
from schemas.manager import (
|
||||
@@ -56,10 +58,18 @@ def register_manager(
|
||||
db: Session = Depends(get_db),
|
||||
_admin=Depends(get_current_admin),
|
||||
):
|
||||
if db.query(ManagerAccount).filter(ManagerAccount.email == body.email).first():
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered")
|
||||
|
||||
existing = db.query(ManagerAccount).filter(ManagerAccount.email == body.email).first()
|
||||
sites = db.query(Site).filter(Site.id.in_(body.site_ids)).all()
|
||||
|
||||
if existing:
|
||||
# Email already exists — just add the new site access links, don't recreate the account
|
||||
for site in sites:
|
||||
if site not in existing.sites:
|
||||
existing.sites.append(site)
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
return existing
|
||||
|
||||
manager = ManagerAccount(
|
||||
email=body.email,
|
||||
password_hash=_pwd.hash(body.password),
|
||||
@@ -89,3 +99,52 @@ def login_manager(body: ManagerLoginRequest, db: Session = Depends(get_db)):
|
||||
@router.post("/refresh", response_model=ManagerTokenOut)
|
||||
def refresh_manager_token(manager: ManagerAccount = Depends(_get_current_manager)):
|
||||
return ManagerTokenOut(access_token=_create_manager_token(manager))
|
||||
|
||||
|
||||
# ── Admin-only: list managers for a site ─────────────────────────────────────
|
||||
|
||||
class ManagerBySiteOut(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
full_name: Optional[str] = None
|
||||
is_active: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
@router.get("/by-site/{site_id}", response_model=list[ManagerBySiteOut])
|
||||
def get_managers_by_site(
|
||||
site_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_admin=Depends(get_current_admin),
|
||||
):
|
||||
site = db.query(Site).filter(Site.id == site_id).first()
|
||||
if not site:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
return site.manager_accounts
|
||||
|
||||
|
||||
# ── Admin-only: remove a manager's access to a site ──────────────────────────
|
||||
|
||||
class SiteAccessRemoveRequest(BaseModel):
|
||||
manager_id: int
|
||||
site_id: int
|
||||
|
||||
|
||||
@router.delete("/site-access", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def remove_manager_site_access(
|
||||
body: SiteAccessRemoveRequest,
|
||||
db: Session = Depends(get_db),
|
||||
_admin=Depends(get_current_admin),
|
||||
):
|
||||
manager = db.query(ManagerAccount).filter(ManagerAccount.id == body.manager_id).first()
|
||||
if not manager:
|
||||
raise HTTPException(status_code=404, detail="Manager not found")
|
||||
|
||||
site = db.query(Site).filter(Site.id == body.site_id).first()
|
||||
if not site:
|
||||
raise HTTPException(status_code=404, detail="Site not found")
|
||||
|
||||
if site in manager.sites:
|
||||
manager.sites.remove(site)
|
||||
db.commit()
|
||||
|
||||
Reference in New Issue
Block a user