This commit is contained in:
Mikan
2026-06-19 16:31:45 +03:00
parent d0d1f003ae
commit 5a78def096
21 changed files with 1250 additions and 548 deletions

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from typing import Any, Dict, List
from uuid import UUID
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -142,3 +143,32 @@ async def list_users(
}
for u in users
]
@router.post("/users/{user_id}/set-active")
async def set_user_active(
user_id: UUID,
payload: Dict[str, Any] = Body(default={}),
db: AsyncSession = Depends(get_db_dep),
admin: User = Depends(require_admin),
):
"""Activate or ban a user. Banned users cannot log in (see auth.login).
Body: `{"is_active": true|false}`. Admins cannot ban themselves.
"""
is_active = bool(payload.get("is_active"))
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalars().first()
if not user:
raise HTTPException(status_code=404, detail="user_not_found")
if user.id == admin.id and not is_active:
raise HTTPException(status_code=400, detail="cannot_ban_self")
user.is_active = is_active
await db.commit()
return {
"id": str(user.id),
"email": user.email,
"username": user.username,
"is_admin": user.is_admin,
"is_active": user.is_active,
}

View File

@@ -36,7 +36,13 @@ async def register(payload: UserRegister, db: AsyncSession = Depends(get_db_dep)
@router.post("/login", response_model=TokenOut)
async def login(payload: UserLogin, db: AsyncSession = Depends(get_db_dep)):
result = await db.execute(select(User).where(User.email == payload.email))
# Accept either email or username in the `login` field.
login_value = (payload.login or "").strip()
if not login_value:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="login_required")
result = await db.execute(
select(User).where((User.email == login_value) | (User.username == login_value))
)
user = result.scalars().first()
if not user or not verify_password(payload.password, user.hashed_password):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid_credentials")