diff --git a/backend/open_webui/migrations/versions/3af16a1c9fb6_update_user_table.py b/backend/open_webui/migrations/versions/3af16a1c9fb6_update_user_table.py new file mode 100644 index 0000000000..ab980f27ce --- /dev/null +++ b/backend/open_webui/migrations/versions/3af16a1c9fb6_update_user_table.py @@ -0,0 +1,32 @@ +"""update user table + +Revision ID: 3af16a1c9fb6 +Revises: 018012973d35 +Create Date: 2025-08-21 02:07:18.078283 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "3af16a1c9fb6" +down_revision: Union[str, None] = "018012973d35" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column("user", sa.Column("username", sa.String(length=50), nullable=True)) + op.add_column("user", sa.Column("bio", sa.Text(), nullable=True)) + op.add_column("user", sa.Column("gender", sa.Text(), nullable=True)) + op.add_column("user", sa.Column("date_of_birth", sa.Date(), nullable=True)) + + +def downgrade() -> None: + op.drop_column("user", "username") + op.drop_column("user", "bio") + op.drop_column("user", "gender") + op.drop_column("user", "date_of_birth") diff --git a/backend/open_webui/models/auths.py b/backend/open_webui/models/auths.py index 3ad88bc119..6517e21345 100644 --- a/backend/open_webui/models/auths.py +++ b/backend/open_webui/models/auths.py @@ -73,11 +73,6 @@ class ProfileImageUrlForm(BaseModel): profile_image_url: str -class UpdateProfileForm(BaseModel): - profile_image_url: str - name: str - - class UpdatePasswordForm(BaseModel): password: str new_password: str diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index 31a5938bfe..620a746eed 100644 --- a/backend/open_webui/models/users.py +++ b/backend/open_webui/models/users.py @@ -11,9 +11,10 @@ from open_webui.utils.misc import throttle from pydantic import BaseModel, ConfigDict -from sqlalchemy import BigInteger, Column, String, Text +from sqlalchemy import BigInteger, Column, String, Text, Date from sqlalchemy import or_ +import datetime #################### # User DB Schema @@ -25,20 +26,28 @@ class User(Base): id = Column(String, primary_key=True) name = Column(String) + email = Column(String) + username = Column(String(50), nullable=True) + role = Column(String) profile_image_url = Column(Text) - last_active_at = Column(BigInteger) - updated_at = Column(BigInteger) - created_at = Column(BigInteger) + bio = Column(Text, nullable=True) + gender = Column(Text, nullable=True) + date_of_birth = Column(Date, nullable=True) + + info = Column(JSONField, nullable=True) + settings = Column(JSONField, nullable=True) api_key = Column(String, nullable=True, unique=True) - settings = Column(JSONField, nullable=True) - info = Column(JSONField, nullable=True) - oauth_sub = Column(Text, unique=True) + last_active_at = Column(BigInteger) + + updated_at = Column(BigInteger) + created_at = Column(BigInteger) + class UserSettings(BaseModel): ui: Optional[dict] = {} @@ -49,20 +58,27 @@ class UserSettings(BaseModel): class UserModel(BaseModel): id: str name: str + email: str + username: Optional[str] = None + role: str = "pending" profile_image_url: str + bio: Optional[str] = None + gender: Optional[str] = None + date_of_birth: Optional[datetime.date] = None + + info: Optional[dict] = None + settings: Optional[UserSettings] = None + + api_key: Optional[str] = None + oauth_sub: Optional[str] = None + last_active_at: int # timestamp in epoch updated_at: int # timestamp in epoch created_at: int # timestamp in epoch - api_key: Optional[str] = None - settings: Optional[UserSettings] = None - info: Optional[dict] = None - - oauth_sub: Optional[str] = None - model_config = ConfigDict(from_attributes=True) @@ -71,6 +87,14 @@ class UserModel(BaseModel): #################### +class UpdateProfileForm(BaseModel): + profile_image_url: str + name: str + bio: Optional[str] = None + gender: Optional[str] = None + date_of_birth: Optional[datetime.date] = None + + class UserListResponse(BaseModel): users: list[UserModel] total: int @@ -349,7 +373,8 @@ class UsersTable: user = db.query(User).filter_by(id=id).first() return UserModel.model_validate(user) # return UserModel(**user.dict()) - except Exception: + except Exception as e: + print(e) return None def update_user_settings_by_id(self, id: str, updated: dict) -> Optional[UserModel]: diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 78a576d202..11254ec78c 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -15,10 +15,9 @@ from open_webui.models.auths import ( SigninResponse, SignupForm, UpdatePasswordForm, - UpdateProfileForm, UserResponse, ) -from open_webui.models.users import Users +from open_webui.models.users import Users, UpdateProfileForm from open_webui.models.groups import Groups from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES @@ -73,7 +72,13 @@ class SessionUserResponse(Token, UserResponse): permissions: Optional[dict] = None -@router.get("/", response_model=SessionUserResponse) +class SessionUserInfoResponse(SessionUserResponse): + bio: Optional[str] = None + gender: Optional[str] = None + date_of_birth: Optional[datetime.date] = None + + +@router.get("/", response_model=SessionUserInfoResponse) async def get_session_user( request: Request, response: Response, user=Depends(get_current_user) ): @@ -121,6 +126,9 @@ async def get_session_user( "name": user.name, "role": user.role, "profile_image_url": user.profile_image_url, + "bio": user.bio, + "gender": user.gender, + "date_of_birth": user.date_of_birth, "permissions": user_permissions, } @@ -137,7 +145,7 @@ async def update_profile( if session_user: user = Users.update_user_by_id( session_user.id, - {"profile_image_url": form_data.profile_image_url, "name": form_data.name}, + form_data.model_dump(), ) if user: return user diff --git a/src/lib/apis/auths/index.ts b/src/lib/apis/auths/index.ts index 0475df8d07..5450479af5 100644 --- a/src/lib/apis/auths/index.ts +++ b/src/lib/apis/auths/index.ts @@ -393,7 +393,7 @@ export const addUser = async ( return res; }; -export const updateUserProfile = async (token: string, name: string, profileImageUrl: string) => { +export const updateUserProfile = async (token: string, profile: object) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/auths/update/profile`, { @@ -403,8 +403,7 @@ export const updateUserProfile = async (token: string, name: string, profileImag ...(token && { authorization: `Bearer ${token}` }) }, body: JSON.stringify({ - name: name, - profile_image_url: profileImageUrl + ...profile }) }) .then(async (res) => { diff --git a/src/lib/components/chat/Settings/Account.svelte b/src/lib/components/chat/Settings/Account.svelte index 6b3c64be75..04ea315d24 100644 --- a/src/lib/components/chat/Settings/Account.svelte +++ b/src/lib/components/chat/Settings/Account.svelte @@ -14,16 +14,23 @@ import Tooltip from '$lib/components/common/Tooltip.svelte'; import SensitiveInput from '$lib/components/common/SensitiveInput.svelte'; import Textarea from '$lib/components/common/Textarea.svelte'; + import { getUserById } from '$lib/apis/users'; const i18n = getContext('i18n'); export let saveHandler: Function; export let saveSettings: Function; + let loaded = false; + let profileImageUrl = ''; let name = ''; let bio = ''; + let _gender = ''; + let gender = ''; + let dateOfBirth = ''; + let webhookUrl = ''; let showAPIKeys = false; @@ -49,11 +56,15 @@ }); } - const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch( - (error) => { - toast.error(`${error}`); - } - ); + const updatedUser = await updateUserProfile(localStorage.token, { + name: name, + profile_image_url: profileImageUrl, + bio: bio ? bio : null, + gender: gender ? gender : null, + date_of_birth: dateOfBirth ? dateOfBirth : null + }).catch((error) => { + toast.error(`${error}`); + }); if (updatedUser) { // Get Session User Info @@ -78,14 +89,30 @@ }; onMount(async () => { - name = $user?.name; - profileImageUrl = $user?.profile_image_url; + const user = await getSessionUser(localStorage.token).catch((error) => { + toast.error(`${error}`); + return null; + }); + + if (user) { + name = user?.name ?? ''; + profileImageUrl = user?.profile_image_url ?? ''; + bio = user?.bio ?? ''; + + _gender = user?.gender ?? ''; + gender = _gender; + + dateOfBirth = user?.date_of_birth ?? ''; + } + webhookUrl = $settings?.notifications?.webhook_url ?? ''; APIKey = await getAPIKey(localStorage.token).catch((error) => { console.log(error); return ''; }); + + loaded = true; }); @@ -164,7 +191,7 @@ -