206 lines
5.7 KiB
Python
206 lines
5.7 KiB
Python
from datetime import datetime
|
|
from typing import Annotated
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
|
from sqlmodel import Session, SQLModel, select
|
|
|
|
from db import get_session
|
|
from innercontext.api.auth_deps import require_admin
|
|
from innercontext.api.utils import get_or_404
|
|
from innercontext.models import (
|
|
Household,
|
|
HouseholdMembership,
|
|
HouseholdRole,
|
|
Role,
|
|
User,
|
|
)
|
|
|
|
router = APIRouter(dependencies=[Depends(require_admin)])
|
|
SessionDep = Annotated[Session, Depends(get_session)]
|
|
|
|
|
|
class AdminHouseholdPublic(SQLModel):
|
|
id: UUID
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class AdminHouseholdMembershipPublic(SQLModel):
|
|
id: UUID
|
|
user_id: UUID
|
|
household_id: UUID
|
|
role: HouseholdRole
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class AdminUserPublic(SQLModel):
|
|
id: UUID
|
|
oidc_issuer: str
|
|
oidc_subject: str
|
|
role: Role
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
household_membership: AdminHouseholdMembershipPublic | None = None
|
|
|
|
|
|
class AdminHouseholdMembershipCreate(SQLModel):
|
|
user_id: UUID
|
|
role: HouseholdRole = HouseholdRole.MEMBER
|
|
|
|
|
|
def _membership_public(
|
|
membership: HouseholdMembership,
|
|
) -> AdminHouseholdMembershipPublic:
|
|
return AdminHouseholdMembershipPublic(
|
|
id=membership.id,
|
|
user_id=membership.user_id,
|
|
household_id=membership.household_id,
|
|
role=membership.role,
|
|
created_at=membership.created_at,
|
|
updated_at=membership.updated_at,
|
|
)
|
|
|
|
|
|
def _household_public(household: Household) -> AdminHouseholdPublic:
|
|
return AdminHouseholdPublic(
|
|
id=household.id,
|
|
created_at=household.created_at,
|
|
updated_at=household.updated_at,
|
|
)
|
|
|
|
|
|
def _user_public(
|
|
user: User,
|
|
membership: HouseholdMembership | None,
|
|
) -> AdminUserPublic:
|
|
return AdminUserPublic(
|
|
id=user.id,
|
|
oidc_issuer=user.oidc_issuer,
|
|
oidc_subject=user.oidc_subject,
|
|
role=user.role,
|
|
created_at=user.created_at,
|
|
updated_at=user.updated_at,
|
|
household_membership=(
|
|
_membership_public(membership) if membership is not None else None
|
|
),
|
|
)
|
|
|
|
|
|
def _get_membership_for_user(
|
|
session: Session,
|
|
user_id: UUID,
|
|
) -> HouseholdMembership | None:
|
|
return session.exec(
|
|
select(HouseholdMembership).where(HouseholdMembership.user_id == user_id)
|
|
).first()
|
|
|
|
|
|
@router.get("/users", response_model=list[AdminUserPublic])
|
|
def list_users(session: SessionDep):
|
|
users = sorted(
|
|
session.exec(select(User)).all(),
|
|
key=lambda user: (user.created_at, str(user.id)),
|
|
)
|
|
memberships = session.exec(select(HouseholdMembership)).all()
|
|
memberships_by_user_id = {
|
|
membership.user_id: membership for membership in memberships
|
|
}
|
|
return [_user_public(user, memberships_by_user_id.get(user.id)) for user in users]
|
|
|
|
|
|
@router.post(
|
|
"/households",
|
|
response_model=AdminHouseholdPublic,
|
|
status_code=status.HTTP_201_CREATED,
|
|
)
|
|
def create_household(session: SessionDep):
|
|
household = Household()
|
|
session.add(household)
|
|
session.commit()
|
|
session.refresh(household)
|
|
return _household_public(household)
|
|
|
|
|
|
@router.post(
|
|
"/households/{household_id}/members",
|
|
response_model=AdminHouseholdMembershipPublic,
|
|
status_code=status.HTTP_201_CREATED,
|
|
)
|
|
def assign_household_member(
|
|
household_id: UUID,
|
|
payload: AdminHouseholdMembershipCreate,
|
|
session: SessionDep,
|
|
):
|
|
_ = get_or_404(session, Household, household_id)
|
|
_ = get_or_404(session, User, payload.user_id)
|
|
existing_membership = _get_membership_for_user(session, payload.user_id)
|
|
if existing_membership is not None:
|
|
detail = "User already belongs to a household"
|
|
if existing_membership.household_id == household_id:
|
|
detail = "User already belongs to this household"
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=detail)
|
|
|
|
membership = HouseholdMembership(
|
|
user_id=payload.user_id,
|
|
household_id=household_id,
|
|
role=payload.role,
|
|
)
|
|
session.add(membership)
|
|
session.commit()
|
|
session.refresh(membership)
|
|
return _membership_public(membership)
|
|
|
|
|
|
@router.patch(
|
|
"/households/{household_id}/members/{user_id}",
|
|
response_model=AdminHouseholdMembershipPublic,
|
|
)
|
|
def move_household_member(
|
|
household_id: UUID,
|
|
user_id: UUID,
|
|
session: SessionDep,
|
|
):
|
|
_ = get_or_404(session, Household, household_id)
|
|
_ = get_or_404(session, User, user_id)
|
|
membership = _get_membership_for_user(session, user_id)
|
|
if membership is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="HouseholdMembership not found",
|
|
)
|
|
if membership.household_id == household_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail="User already belongs to this household",
|
|
)
|
|
|
|
membership.household_id = household_id
|
|
session.add(membership)
|
|
session.commit()
|
|
session.refresh(membership)
|
|
return _membership_public(membership)
|
|
|
|
|
|
@router.delete(
|
|
"/households/{household_id}/members/{user_id}",
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
)
|
|
def remove_household_member(
|
|
household_id: UUID,
|
|
user_id: UUID,
|
|
session: SessionDep,
|
|
):
|
|
_ = get_or_404(session, Household, household_id)
|
|
_ = get_or_404(session, User, user_id)
|
|
membership = _get_membership_for_user(session, user_id)
|
|
if membership is None or membership.household_id != household_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="HouseholdMembership not found",
|
|
)
|
|
|
|
session.delete(membership)
|
|
session.commit()
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|