Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion backend/api/app/routers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
from app.db import get_db
from app.services import user_service
from shared.models import User
from shared.schemas import PasswordChange, UserCreate, UserRead, UserUpdate
from shared.schemas import (
PasswordChange,
UserCreate,
UserRead,
UserSelfUpdate,
UserUpdate,
)

router = APIRouter()

Expand All @@ -24,6 +30,15 @@ async def get_me(user: User = Depends(get_current_user)) -> User:
return user


@router.patch("/me", response_model=UserRead)
async def update_me(
body: UserSelfUpdate,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
) -> User:
return await user_service.update_me(db, user, body)


@router.post("/me/password", status_code=status.HTTP_204_NO_CONTENT)
async def change_password(
body: PasswordChange,
Expand Down
19 changes: 17 additions & 2 deletions backend/api/app/services/user_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from app.repositories import user_repo
from shared.enums import IdentityProvider
from shared.models import User, UserIdentity
from shared.schemas import PasswordChange, UserCreate, UserUpdate
from shared.schemas import PasswordChange, UserCreate, UserSelfUpdate, UserUpdate


async def create_user(db: AsyncSession, body: UserCreate) -> User:
Expand All @@ -34,6 +34,16 @@ async def list_users(db: AsyncSession) -> list[User]:
return await user_repo.list_all(db)


async def update_me(
db: AsyncSession, user: User, body: UserSelfUpdate
) -> User:
"""Update the current user's own profile fields (name only)."""
await user_repo.update(db, user, body.model_dump(exclude_unset=True))
await db.commit()
await db.refresh(user)
return user


async def update_user(
db: AsyncSession, user_id: str, body: UserUpdate, current_user: User
) -> User:
Expand All @@ -43,7 +53,12 @@ async def update_user(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Cannot deactivate your own account",
)
if body.role is not None and body.role != current_user.role:
# Compare by value to avoid enum-vs-string mismatch across the
# Pydantic/SQLAlchemy boundary.
if body.role is not None and (
getattr(body.role, "value", body.role)
!= getattr(current_user.role, "value", current_user.role)
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Cannot change your own role",
Expand Down
6 changes: 6 additions & 0 deletions backend/shared/shared/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ class UserUpdate(BaseModel):
is_active: bool | None = None


class UserSelfUpdate(BaseModel):
"""Fields a user may change on their own account. Excludes role / is_active."""

name: str | None = None


class PasswordChange(BaseModel):
current_password: str
new_password: str
Expand Down
Loading
Loading