Phase 4 of Migration
This commit is contained in:
@@ -6,6 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from staff.orm import Staff
|
||||
from auth.utils import hash_password
|
||||
from auth.models import default_permissions_for_role
|
||||
from shared.audit import log_action, diff
|
||||
from shared.exceptions import NotFoundError, AuthorizationError
|
||||
import uuid
|
||||
|
||||
@@ -17,7 +18,7 @@ def _now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def _to_response(staff: Staff) -> dict:
|
||||
def _to_dict(staff: Staff) -> dict:
|
||||
return {
|
||||
"id": staff.id,
|
||||
"email": staff.email,
|
||||
@@ -28,6 +29,10 @@ def _to_response(staff: Staff) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _to_response(staff: Staff) -> dict:
|
||||
return _to_dict(staff)
|
||||
|
||||
|
||||
async def list_staff(db: AsyncSession, search: str = None, role_filter: str = None) -> dict:
|
||||
stmt = select(Staff)
|
||||
if role_filter:
|
||||
@@ -58,7 +63,13 @@ async def get_staff_me(db: AsyncSession, user_sub: str) -> dict:
|
||||
return await get_staff(db, user_sub)
|
||||
|
||||
|
||||
async def create_staff(db: AsyncSession, data: dict, current_user_role: str) -> dict:
|
||||
async def create_staff(
|
||||
db: AsyncSession,
|
||||
data: dict,
|
||||
current_user_role: str,
|
||||
actor_id: str,
|
||||
actor_name: str,
|
||||
) -> dict:
|
||||
role = data.get("role", "user")
|
||||
if role not in VALID_ROLES:
|
||||
raise AuthorizationError(f"Invalid role: {role}")
|
||||
@@ -86,10 +97,23 @@ async def create_staff(db: AsyncSession, data: dict, current_user_role: str) ->
|
||||
hashed_password=hash_password(data["password"]),
|
||||
is_active=True,
|
||||
permissions=permissions,
|
||||
ui_prefs={},
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db.add(staff)
|
||||
await db.flush()
|
||||
|
||||
await log_action(
|
||||
db,
|
||||
actor_id=actor_id,
|
||||
actor_name=actor_name,
|
||||
action="CREATE",
|
||||
entity_type="staff",
|
||||
entity_id=uid,
|
||||
entity_label=data["email"],
|
||||
meta={"role": role},
|
||||
)
|
||||
await db.commit()
|
||||
await db.refresh(staff)
|
||||
return _to_response(staff)
|
||||
@@ -101,6 +125,8 @@ async def update_staff(
|
||||
data: dict,
|
||||
current_user_role: str,
|
||||
current_user_id: str,
|
||||
actor_id: str,
|
||||
actor_name: str,
|
||||
) -> dict:
|
||||
result = await db.execute(select(Staff).where(Staff.id == staff_id).limit(1))
|
||||
staff = result.scalar_one_or_none()
|
||||
@@ -112,6 +138,8 @@ async def update_staff(
|
||||
if current_user_role == "admin" and data.get("role") == "sysadmin":
|
||||
raise AuthorizationError("Admin cannot promote to sysadmin")
|
||||
|
||||
old = _to_dict(staff)
|
||||
|
||||
if data.get("email") is not None:
|
||||
dup = await db.execute(
|
||||
select(Staff).where(Staff.email == data["email"], Staff.id != staff_id).limit(1)
|
||||
@@ -132,6 +160,20 @@ async def update_staff(
|
||||
staff.permissions = data["permissions"]
|
||||
|
||||
staff.updated_at = _now()
|
||||
await db.flush()
|
||||
|
||||
changes = diff(old, _to_dict(staff))
|
||||
action = "PERMISSION_CHANGE" if "permissions" in data and len(changes) == 1 else "UPDATE"
|
||||
await log_action(
|
||||
db,
|
||||
actor_id=actor_id,
|
||||
actor_name=actor_name,
|
||||
action=action,
|
||||
entity_type="staff",
|
||||
entity_id=staff_id,
|
||||
entity_label=staff.email,
|
||||
changes=changes or None,
|
||||
)
|
||||
await db.commit()
|
||||
await db.refresh(staff)
|
||||
return _to_response(staff)
|
||||
@@ -142,6 +184,8 @@ async def update_staff_password(
|
||||
staff_id: str,
|
||||
new_password: str,
|
||||
current_user_role: str,
|
||||
actor_id: str,
|
||||
actor_name: str,
|
||||
) -> dict:
|
||||
result = await db.execute(select(Staff).where(Staff.id == staff_id).limit(1))
|
||||
staff = result.scalar_one_or_none()
|
||||
@@ -152,12 +196,23 @@ async def update_staff_password(
|
||||
|
||||
staff.hashed_password = hash_password(new_password)
|
||||
staff.updated_at = _now()
|
||||
await db.flush()
|
||||
|
||||
await log_action(
|
||||
db,
|
||||
actor_id=actor_id,
|
||||
actor_name=actor_name,
|
||||
action="UPDATE",
|
||||
entity_type="staff",
|
||||
entity_id=staff_id,
|
||||
entity_label=staff.email,
|
||||
meta={"detail": "password changed"},
|
||||
)
|
||||
await db.commit()
|
||||
return {"message": "Password updated successfully"}
|
||||
|
||||
|
||||
async def get_preferences(db: AsyncSession, staff_id: str) -> dict:
|
||||
"""Return ui_prefs JSONB for a staff member."""
|
||||
result = await db.execute(select(Staff).where(Staff.id == staff_id).limit(1))
|
||||
staff = result.scalar_one_or_none()
|
||||
if staff is None:
|
||||
@@ -166,7 +221,6 @@ async def get_preferences(db: AsyncSession, staff_id: str) -> dict:
|
||||
|
||||
|
||||
async def update_preferences(db: AsyncSession, staff_id: str, page_key: str, prefs: dict) -> dict:
|
||||
"""Merge page-level preferences into the staff member's ui_prefs column."""
|
||||
result = await db.execute(select(Staff).where(Staff.id == staff_id).limit(1))
|
||||
staff = result.scalar_one_or_none()
|
||||
if staff is None:
|
||||
@@ -185,6 +239,8 @@ async def delete_staff(
|
||||
staff_id: str,
|
||||
current_user_role: str,
|
||||
current_user_id: str,
|
||||
actor_id: str,
|
||||
actor_name: str,
|
||||
) -> dict:
|
||||
if staff_id == current_user_id:
|
||||
raise AuthorizationError("Cannot delete your own account")
|
||||
@@ -197,6 +253,16 @@ async def delete_staff(
|
||||
if current_user_role == "admin" and staff.role == "sysadmin":
|
||||
raise AuthorizationError("Admin cannot delete sysadmin accounts")
|
||||
|
||||
label = staff.email
|
||||
await log_action(
|
||||
db,
|
||||
actor_id=actor_id,
|
||||
actor_name=actor_name,
|
||||
action="DELETE",
|
||||
entity_type="staff",
|
||||
entity_id=staff_id,
|
||||
entity_label=label,
|
||||
)
|
||||
await db.delete(staff)
|
||||
await db.commit()
|
||||
return {"message": "Staff member deleted"}
|
||||
|
||||
Reference in New Issue
Block a user