feat: implement full admin panel with dashboard, user management, and platform settings

Adds complete super_admin panel with 9 pages and account owner categories page.
Backend includes 5 new DB tables, ~25 API endpoints, settings manager with
in-memory cache, and 29 integration tests. Frontend includes reusable admin
components (DataTable, Pagination, ActionMenu, etc.) with code-split lazy loading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-08 06:05:59 -05:00
parent 4f57c84d43
commit b570f8415f
50 changed files with 4589 additions and 5 deletions

View File

@@ -7,7 +7,9 @@ from sqlalchemy import select, func
from app.core.database import get_db
from app.core.audit import log_audit
from app.models.user import User
from app.models.account import Account
from app.schemas.user import UserResponse, RoleUpdate, AccountRoleUpdate
from app.schemas.admin import MoveUserAccount
from app.api.deps import require_admin
router = APIRouter(prefix="/admin", tags=["admin"])
@@ -167,3 +169,32 @@ async def activate_user(
await db.commit()
await db.refresh(user)
return user
@router.put("/users/{user_id}/move-account", response_model=UserResponse)
async def move_user_account(
user_id: UUID,
data: MoveUserAccount,
db: Annotated[AsyncSession, Depends(get_db)],
current_user: Annotated[User, Depends(require_admin)],
):
"""Move a user to a different account (super admin only)."""
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
result = await db.execute(select(Account).where(Account.display_code == data.display_code))
target_account = result.scalar_one_or_none()
if not target_account:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Target account not found")
old_account_id = user.account_id
user.account_id = target_account.id
user.account_role = "engineer" # Reset to engineer on move
await log_audit(db, current_user.id, "user.move_account", "user", user.id,
{"old_account_id": str(old_account_id), "new_account_id": str(target_account.id)})
await db.commit()
await db.refresh(user)
return user