Files
member-backend/app/api/admin_entities.py

266 lines
9.6 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.models.api_client import ApiClient
from app.repositories.member_organizations_repo import MemberOrganizationsRepository
from app.repositories.organizations_repo import OrganizationsRepository
from app.repositories.users_repo import UsersRepository
from app.schemas.members import (
MemberCreateRequest,
MemberListResponse,
MemberOrganizationsResponse,
MemberSummary,
MemberUpdateRequest,
)
from app.schemas.organizations import (
OrganizationCreateRequest,
OrganizationListResponse,
OrganizationSummary,
OrganizationUpdateRequest,
)
from app.security.api_client_auth import require_api_client
router = APIRouter(prefix="/admin", tags=["admin"])
def _to_member_summary(member) -> MemberSummary:
return MemberSummary(
id=member.id,
authentik_sub=member.authentik_sub,
authentik_user_id=member.authentik_user_id,
email=member.email,
display_name=member.display_name,
is_active=member.is_active,
)
def _to_org_summary(org) -> OrganizationSummary:
return OrganizationSummary(
id=org.id,
org_code=org.org_code,
name=org.name,
tax_id=org.tax_id,
status=org.status,
)
@router.get("/organizations", response_model=OrganizationListResponse)
def list_organizations(
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
keyword: str | None = Query(default=None),
status_filter: str | None = Query(default=None, alias="status"),
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
) -> OrganizationListResponse:
repo = OrganizationsRepository(db)
items, total = repo.list(keyword=keyword, status=status_filter, limit=limit, offset=offset)
return OrganizationListResponse(items=[_to_org_summary(i) for i in items], total=total, limit=limit, offset=offset)
@router.post("/organizations", response_model=OrganizationSummary)
def create_organization(
payload: OrganizationCreateRequest,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> OrganizationSummary:
repo = OrganizationsRepository(db)
existing = repo.get_by_code(payload.org_code)
if existing:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="organization_code_already_exists")
org = repo.create(org_code=payload.org_code, name=payload.name, tax_id=payload.tax_id, status=payload.status)
return _to_org_summary(org)
@router.get("/organizations/{org_id}", response_model=OrganizationSummary)
def get_organization(
org_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> OrganizationSummary:
repo = OrganizationsRepository(db)
org = repo.get_by_id(org_id)
if not org:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="organization_not_found")
return _to_org_summary(org)
@router.patch("/organizations/{org_id}", response_model=OrganizationSummary)
def update_organization(
org_id: str,
payload: OrganizationUpdateRequest,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> OrganizationSummary:
repo = OrganizationsRepository(db)
org = repo.get_by_id(org_id)
if not org:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="organization_not_found")
updated = repo.update(org, name=payload.name, tax_id=payload.tax_id, status=payload.status)
return _to_org_summary(updated)
@router.post("/organizations/{org_id}/activate", response_model=OrganizationSummary)
def activate_organization(
org_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> OrganizationSummary:
repo = OrganizationsRepository(db)
org = repo.get_by_id(org_id)
if not org:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="organization_not_found")
return _to_org_summary(repo.update(org, status="active"))
@router.post("/organizations/{org_id}/deactivate", response_model=OrganizationSummary)
def deactivate_organization(
org_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> OrganizationSummary:
repo = OrganizationsRepository(db)
org = repo.get_by_id(org_id)
if not org:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="organization_not_found")
return _to_org_summary(repo.update(org, status="inactive"))
@router.get("/members", response_model=MemberListResponse)
def list_members(
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
keyword: str | None = Query(default=None),
is_active: bool | None = Query(default=None),
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
) -> MemberListResponse:
repo = UsersRepository(db)
items, total = repo.list(keyword=keyword, is_active=is_active, limit=limit, offset=offset)
return MemberListResponse(items=[_to_member_summary(i) for i in items], total=total, limit=limit, offset=offset)
@router.post("/members", response_model=MemberSummary)
def create_member(
payload: MemberCreateRequest,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> MemberSummary:
repo = UsersRepository(db)
existing = repo.get_by_sub(payload.authentik_sub)
if existing:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="member_sub_already_exists")
member = repo.upsert_by_sub(
authentik_sub=payload.authentik_sub,
email=payload.email,
display_name=payload.display_name,
is_active=payload.is_active,
)
return _to_member_summary(member)
@router.get("/members/{member_id}", response_model=MemberSummary)
def get_member(
member_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> MemberSummary:
repo = UsersRepository(db)
member = repo.get_by_id(member_id)
if not member:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="member_not_found")
return _to_member_summary(member)
@router.patch("/members/{member_id}", response_model=MemberSummary)
def update_member(
member_id: str,
payload: MemberUpdateRequest,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> MemberSummary:
repo = UsersRepository(db)
member = repo.get_by_id(member_id)
if not member:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="member_not_found")
updated = repo.update_member(member, email=payload.email, display_name=payload.display_name, is_active=payload.is_active)
return _to_member_summary(updated)
@router.post("/members/{member_id}/activate", response_model=MemberSummary)
def activate_member(
member_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> MemberSummary:
repo = UsersRepository(db)
member = repo.get_by_id(member_id)
if not member:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="member_not_found")
return _to_member_summary(repo.update_member(member, is_active=True))
@router.post("/members/{member_id}/deactivate", response_model=MemberSummary)
def deactivate_member(
member_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> MemberSummary:
repo = UsersRepository(db)
member = repo.get_by_id(member_id)
if not member:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="member_not_found")
return _to_member_summary(repo.update_member(member, is_active=False))
@router.get("/members/{member_id}/organizations", response_model=MemberOrganizationsResponse)
def list_member_organizations(
member_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> MemberOrganizationsResponse:
users_repo = UsersRepository(db)
link_repo = MemberOrganizationsRepository(db)
member = users_repo.get_by_id(member_id)
if not member:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="member_not_found")
orgs = link_repo.list_organizations_by_member_id(member_id)
return MemberOrganizationsResponse(member=_to_member_summary(member), organizations=[_to_org_summary(o) for o in orgs])
@router.post("/members/{member_id}/organizations/{org_id}")
def bind_member_organization(
member_id: str,
org_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> dict[str, str]:
users_repo = UsersRepository(db)
orgs_repo = OrganizationsRepository(db)
link_repo = MemberOrganizationsRepository(db)
member = users_repo.get_by_id(member_id)
if not member:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="member_not_found")
org = orgs_repo.get_by_id(org_id)
if not org:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="organization_not_found")
relation = link_repo.add_if_not_exists(member_id=member_id, organization_id=org_id)
return {"relation_id": relation.id, "result": "bound"}
@router.delete("/members/{member_id}/organizations/{org_id}")
def unbind_member_organization(
member_id: str,
org_id: str,
_: ApiClient = Depends(require_api_client),
db: Session = Depends(get_db),
) -> dict[str, int | str]:
link_repo = MemberOrganizationsRepository(db)
deleted = link_repo.remove(member_id=member_id, organization_id=org_id)
return {"deleted": deleted, "result": "unbound"}