diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/app/api/api_v1/endpoints/users.py index e3ec0fd..f3ddd35 100644 --- a/src/backend/app/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/app/api/api_v1/endpoints/users.py @@ -10,8 +10,10 @@ from app.api.deps import ( get_current_active_superuser, ) from app.core.config import settings +from app.core.security import get_password_hash, verify_password from app.models import ( Message, + UpdatePassword, User, UserCreate, UserCreateOpen, @@ -60,24 +62,40 @@ def create_user(*, session: SessionDep, user_in: UserCreate) -> Any: return user -@router.put("/me", response_model=UserOut) +@router.patch("/me", response_model=UserOut) def update_user_me( - *, session: SessionDep, body: UserUpdateMe, current_user: CurrentUser + *, session: SessionDep, user_in: UserUpdateMe, current_user: CurrentUser ) -> Any: """ Update own user. """ - # TODO: Refactor when SQLModel has update - # current_user_data = jsonable_encoder(current_user) - # user_in = UserUpdate(**current_user_data) - # if password is not None: - # user_in.password = password - # if full_name is not None: - # user_in.full_name = full_name - # if email is not None: - # user_in.email = email - # user = crud.user.update(session, session_obj=current_user, obj_in=user_in) - # return user + + user_data = user_in.model_dump(exclude_unset=True) + current_user.sqlmodel_update(user_data) + session.add(current_user) + session.commit() + session.refresh(current_user) + return current_user + + +@router.patch("/me/password", response_model=Message) +def update_password_me( + *, session: SessionDep, body: UpdatePassword, current_user: CurrentUser +) -> Any: + """ + Update own password. + """ + if not verify_password(body.current_password, current_user.hashed_password): + raise HTTPException(status_code=400, detail="Incorrect password") + if body.current_password == body.new_password: + raise HTTPException( + status_code=400, detail="New password cannot be the same as the current one" + ) + hashed_password = get_password_hash(body.new_password) + current_user.hashed_password = hashed_password + session.add(current_user) + session.commit() + return Message(message="Password updated successfully") @router.get("/me", response_model=UserOut) @@ -128,7 +146,7 @@ def read_user_by_id( return user -@router.put( +@router.patch( "/{user_id}", dependencies=[Depends(get_current_active_superuser)], response_model=UserOut, @@ -143,15 +161,23 @@ def update_user( Update a user. """ - # TODO: Refactor when SQLModel has update - # user = session.get(User, user_id) - # if not user: - # raise HTTPException( - # status_code=404, - # detail="The user with this username does not exist in the system", - # ) - # user = crud.user.update(session, db_obj=user, obj_in=user_in) - # return user + db_user = session.get(User, user_id) + if not db_user: + raise HTTPException( + status_code=404, + detail="The user with this username does not exist in the system", + ) + user_data = user_in.model_dump(exclude_unset=True) + extra_data = {} + if "password" in user_data: + password = user_data["password"] + hashed_password = get_password_hash(password) + extra_data["hashed_password"] = hashed_password + db_user.sqlmodel_update(user_data, update=extra_data) + session.add(db_user) + session.commit() + session.refresh(db_user) + return db_user @router.delete("/{user_id}") diff --git a/src/backend/app/app/models.py b/src/backend/app/app/models.py index 505f8b9..9186b58 100644 --- a/src/backend/app/app/models.py +++ b/src/backend/app/app/models.py @@ -30,11 +30,15 @@ class UserUpdate(UserBase): class UserUpdateMe(SQLModel): - password: Union[str, None] = None full_name: Union[str, None] = None email: Union[EmailStr, None] = None +class UpdatePassword(SQLModel): + current_password: str + new_password: str + + # Database model, database table inferred from class name class User(UserBase, table=True): id: Union[int, None] = Field(default=None, primary_key=True) diff --git a/src/new-frontend/src/client/index.ts b/src/new-frontend/src/client/index.ts index 59b2403..adc379d 100644 --- a/src/new-frontend/src/client/index.ts +++ b/src/new-frontend/src/client/index.ts @@ -15,6 +15,7 @@ export type { ItemUpdate } from './models/ItemUpdate'; export type { Message } from './models/Message'; export type { NewPassword } from './models/NewPassword'; export type { Token } from './models/Token'; +export type { UpdatePassword } from './models/UpdatePassword'; export type { UserCreate } from './models/UserCreate'; export type { UserCreateOpen } from './models/UserCreateOpen'; export type { UserOut } from './models/UserOut'; @@ -30,6 +31,7 @@ export { $ItemUpdate } from './schemas/$ItemUpdate'; export { $Message } from './schemas/$Message'; export { $NewPassword } from './schemas/$NewPassword'; export { $Token } from './schemas/$Token'; +export { $UpdatePassword } from './schemas/$UpdatePassword'; export { $UserCreate } from './schemas/$UserCreate'; export { $UserCreateOpen } from './schemas/$UserCreateOpen'; export { $UserOut } from './schemas/$UserOut'; diff --git a/src/new-frontend/src/client/models/UpdatePassword.ts b/src/new-frontend/src/client/models/UpdatePassword.ts new file mode 100644 index 0000000..f0c4b69 --- /dev/null +++ b/src/new-frontend/src/client/models/UpdatePassword.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type UpdatePassword = { + current_password: string; + new_password: string; +}; diff --git a/src/new-frontend/src/client/models/UserUpdateMe.ts b/src/new-frontend/src/client/models/UserUpdateMe.ts index bfed1a4..84ee306 100644 --- a/src/new-frontend/src/client/models/UserUpdateMe.ts +++ b/src/new-frontend/src/client/models/UserUpdateMe.ts @@ -4,7 +4,6 @@ /* eslint-disable */ export type UserUpdateMe = { - password?: string; full_name?: string; email?: string; }; diff --git a/src/new-frontend/src/client/schemas/$UpdatePassword.ts b/src/new-frontend/src/client/schemas/$UpdatePassword.ts new file mode 100644 index 0000000..1875881 --- /dev/null +++ b/src/new-frontend/src/client/schemas/$UpdatePassword.ts @@ -0,0 +1,16 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $UpdatePassword = { + properties: { + current_password: { + type: 'string', + isRequired: true, +}, + new_password: { + type: 'string', + isRequired: true, +}, + }, +} as const; diff --git a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts index d1408a1..0aaf6ab 100644 --- a/src/new-frontend/src/client/schemas/$UserUpdateMe.ts +++ b/src/new-frontend/src/client/schemas/$UserUpdateMe.ts @@ -4,9 +4,6 @@ /* eslint-disable */ export const $UserUpdateMe = { properties: { - password: { - type: 'string', -}, full_name: { type: 'string', }, diff --git a/src/new-frontend/src/client/services/UsersService.ts b/src/new-frontend/src/client/services/UsersService.ts index b5911d6..16481fc 100644 --- a/src/new-frontend/src/client/services/UsersService.ts +++ b/src/new-frontend/src/client/services/UsersService.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ import type { Message } from '../models/Message'; +import type { UpdatePassword } from '../models/UpdatePassword'; import type { UserCreate } from '../models/UserCreate'; import type { UserCreateOpen } from '../models/UserCreateOpen'; import type { UserOut } from '../models/UserOut'; @@ -88,7 +89,7 @@ requestBody, requestBody: UserUpdateMe, }): CancelablePromise { return __request(OpenAPI, { - method: 'PUT', + method: 'PATCH', url: '/api/v1/users/me', body: requestBody, mediaType: 'application/json', @@ -98,6 +99,28 @@ requestBody: UserUpdateMe, }); } + /** + * Update Password Me + * Update own password. + * @returns Message Successful Response + * @throws ApiError + */ + public static updatePasswordMe({ +requestBody, +}: { +requestBody: UpdatePassword, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/users/me/password', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** * Create User Open * Create new user without the need to be logged in. @@ -143,33 +166,6 @@ userId: number, }); } - /** - * Update User - * Update a user. - * @returns UserOut Successful Response - * @throws ApiError - */ - public static updateUser({ -userId, -requestBody, -}: { -userId: number, -requestBody: UserUpdate, -}): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/api/v1/users/{user_id}', - path: { - 'user_id': userId, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - /** * Delete User * Delete a user. @@ -193,4 +189,31 @@ userId: number, }); } + /** + * Update User + * Update a user. + * @returns UserOut Successful Response + * @throws ApiError + */ + public static updateUser({ +userId, +requestBody, +}: { +userId: number, +requestBody: UserUpdate, +}): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/v1/users/{user_id}', + path: { + 'user_id': userId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + }