Убрал лишнее

This commit is contained in:
urec56 2024-06-01 20:38:36 +05:00
parent d1e8e80be6
commit 9af53e4982
16 changed files with 78 additions and 187 deletions

View file

@ -1,38 +0,0 @@
from sqladmin.authentication import AuthenticationBackend
from starlette.requests import Request
from app.config import settings
from app.users.auth import create_access_token, ADMIN_USER, AuthService
from app.users.dependencies import get_current_user
class AdminAuth(AuthenticationBackend):
async def login(self, request: Request) -> bool:
form = await request.form()
username, password = form["username"], form["password"]
user = await AuthService.authenticate_user_by_username(username, password)
if user and user.role == ADMIN_USER:
access_token = create_access_token({"sub": str(user.id)})
request.session.update({"token": access_token})
return True
async def logout(self, request: Request) -> bool:
# Usually you'd want to just clear the session
request.session.clear()
return True
async def authenticate(self, request: Request) -> bool:
token = request.session.get("token")
if not token:
return False
user = await get_current_user(token)
if user:
return await AuthService.validate_user_admin(user.id)
return False
authentication_backend = AdminAuth(secret_key=settings.SECRET_KEY)

View file

@ -1,54 +0,0 @@
from sqladmin import ModelView
from app.users.models import Users
from app.chat.models import Chats, UsersXChats, Messages
class UsersAdmin(ModelView, model=Users):
column_list = [
Users.id,
Users.email,
Users.username,
Users.role,
Users.black_phoenix,
Users.avatar_image,
Users.date_of_birth,
Users.date_of_registration,
Users.usersxchats,
]
column_details_exclude_list = [Users.hashed_password]
can_delete = False
name = "Пользователь"
name_plural = "Пользователи"
icon = "fa-solid fa-users"
class ChatsAdmin(ModelView, model=Chats):
column_list = [Chats.id, Chats.chat_for, Chats.usersxchats]
name = "Чат"
name_plural = "Чаты"
icon = "fa-solid fa-comment"
class MessagesAdmin(ModelView, model=Messages):
column_list = [
Messages.id,
Messages.chat_id,
Messages.user_id,
Messages.message,
Messages.image_url,
Messages.created_at,
Messages.visibility,
Messages.user,
]
name = "Сообщение"
name_plural = "Сообщения"
icon = "fa-solid fa-sms"
class UsersXChatsAdmin(ModelView, model=UsersXChats):
column_list = [UsersXChats.user_id, UsersXChats.chat_id]
name = "Допущенный чат"
name_plural = "Допущенные чаты"
icon = "fa-solid fa-list"

View file

@ -4,8 +4,8 @@ from app.dao.base import BaseDAO
from app.database import async_session_maker, engine # noqa
from app.exceptions import UserAlreadyInChatException, UserAlreadyPinnedChatException
from app.chat.shemas import SMessage
from app.users.models import Users
from app.chat.models import Chats, Messages, UsersXChats, PinnedChats, PinnedMessages, Answers
from app.models.users import Users
from app.models.chat import Chats, Messages, UsersXChats, PinnedChats, PinnedMessages, Answers
class ChatDAO(BaseDAO):

View file

@ -10,8 +10,7 @@ from app.chat.shemas import SMessage, SLastMessages, SPinnedMessage, SPinnedChat
from app.users.dao import UserDAO
from app.users.dependencies import check_verificated_user_with_exc
from app.users.auth import ADMIN_USER_ID, AuthService
from app.users.models import Users
from app.users.schemas import SCreateInvitationLink, SUserAddedToChat
from app.users.schemas import SCreateInvitationLink, SUserAddedToChat, SUser
router = APIRouter(prefix="/chat", tags=["Чат"])
@ -21,7 +20,7 @@ router = APIRouter(prefix="/chat", tags=["Чат"])
status_code=status.HTTP_200_OK,
response_model=list[SChat]
)
async def get_all_chats(user: Users = Depends(check_verificated_user_with_exc)):
async def get_all_chats(user: SUser = Depends(check_verificated_user_with_exc)):
result = await UserDAO.get_user_allowed_chats(user.id)
return result
@ -31,7 +30,7 @@ async def get_all_chats(user: Users = Depends(check_verificated_user_with_exc)):
status_code=status.HTTP_201_CREATED,
response_model=None,
)
async def add_message_to_chat(chat_id: int, message: str, user: Users = Depends(check_verificated_user_with_exc)):
async def add_message_to_chat(chat_id: int, message: str, user: SUser = Depends(check_verificated_user_with_exc)):
chats = await AuthService.get_user_allowed_chats_id(user.id)
if chat_id not in chats:
raise UserDontHavePermissionException
@ -48,7 +47,7 @@ async def add_message_to_chat(chat_id: int, message: str, user: Users = Depends(
status_code=status.HTTP_200_OK,
response_model=None,
)
async def delete_message_from_chat(message_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def delete_message_from_chat(message_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
get_message_sender = await ChatDAO.get_message_by_id(message_id=message_id)
if get_message_sender is None:
raise MessageNotFoundException
@ -64,7 +63,7 @@ async def delete_message_from_chat(message_id: int, user: Users = Depends(check_
status_code=status.HTTP_201_CREATED,
response_model=None,
)
async def create_chat(user_to_exclude: int, chat_name: str, user: Users = Depends(check_verificated_user_with_exc)):
async def create_chat(user_to_exclude: int, chat_name: str, user: SUser = Depends(check_verificated_user_with_exc)):
if user.id == user_to_exclude:
raise UserCanNotReadThisChatException
chat_id = await ChatDAO.create(user_id=user_to_exclude, chat_name=chat_name, created_by=user.id)
@ -78,7 +77,7 @@ async def create_chat(user_to_exclude: int, chat_name: str, user: Users = Depend
status_code=status.HTTP_200_OK,
response_model=list[SMessage]
)
async def get_last_message(chat_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def get_last_message(chat_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
message = await ChatDAO.get_some_messages(chat_id=chat_id, message_number_from=0, messages_to_get=1)
if message is None:
@ -94,7 +93,7 @@ async def get_last_message(chat_id: int, user: Users = Depends(check_verificated
response_model=list[SMessage]
)
async def get_some_messages(
chat_id: int, last_messages: SLastMessages = Depends(), user: Users = Depends(check_verificated_user_with_exc)
chat_id: int, last_messages: SLastMessages = Depends(), user: SUser = Depends(check_verificated_user_with_exc)
):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
messages = await ChatDAO.get_some_messages(
@ -113,7 +112,7 @@ async def get_some_messages(
status_code=status.HTTP_200_OK,
response_model=SMessage
)
async def get_message_by_id(chat_id: int, message_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def get_message_by_id(chat_id: int, message_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
message = await ChatDAO.get_message_by_id(message_id=message_id)
if not message:
@ -128,7 +127,7 @@ async def get_message_by_id(chat_id: int, message_id: int, user: Users = Depends
status_code=status.HTTP_201_CREATED,
response_model=SCreateInvitationLink,
)
async def create_invitation_link(chat_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def create_invitation_link(chat_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
cipher_suite = Fernet(settings.INVITATION_LINK_TOKEN_KEY)
invitation_token = cipher_suite.encrypt(str(chat_id).encode())
@ -141,7 +140,7 @@ async def create_invitation_link(chat_id: int, user: Users = Depends(check_verif
status_code=status.HTTP_200_OK,
response_model=SUserAddedToChat,
)
async def invite_to_chat(invitation_token: str, user: Users = Depends(check_verificated_user_with_exc)):
async def invite_to_chat(invitation_token: str, user: SUser = Depends(check_verificated_user_with_exc)):
invitation_token = invitation_token.encode()
cipher_suite = Fernet(settings.INVITATION_LINK_TOKEN_KEY)
chat_id = int(cipher_suite.decrypt(invitation_token))
@ -156,7 +155,7 @@ async def invite_to_chat(invitation_token: str, user: Users = Depends(check_veri
status_code=status.HTTP_200_OK,
response_model=SDeletedChat,
)
async def delete_chat(chat_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def delete_chat(chat_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
chat = await ChatDAO.find_one_or_none(id=chat_id)
if user.id == chat.created_by:
return {"deleted_chat": await ChatDAO.delete_chat(chat_id)}
@ -168,7 +167,7 @@ async def delete_chat(chat_id: int, user: Users = Depends(check_verificated_user
status_code=status.HTTP_200_OK,
response_model=SDeletedUser
)
async def delete_user_from_chat(chat_id: int, user_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def delete_user_from_chat(chat_id: int, user_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
chat = await ChatDAO.find_one_or_none(id=chat_id)
if user.id == chat.created_by:
return {"deleted_user": await ChatDAO.delete_user(chat_id=chat_id, user_id=user_id)}
@ -180,7 +179,7 @@ async def delete_user_from_chat(chat_id: int, user_id: int, user: Users = Depend
status_code=status.HTTP_200_OK,
response_model=SPinnedChat
)
async def pinn_chat(chat_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def pinn_chat(chat_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
await ChatDAO.pinn_chat(chat_id=chat_id, user_id=user.id)
return {"chat_id": chat_id, "user_id": user.id}
@ -191,7 +190,7 @@ async def pinn_chat(chat_id: int, user: Users = Depends(check_verificated_user_w
status_code=status.HTTP_200_OK,
response_model=SPinnedChat
)
async def unpinn_chat(chat_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def unpinn_chat(chat_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
await ChatDAO.unpinn_chat(chat_id=chat_id, user_id=user.id)
return {"chat_id": chat_id, "user_id": user.id}
@ -202,7 +201,7 @@ async def unpinn_chat(chat_id: int, user: Users = Depends(check_verificated_user
status_code=status.HTTP_200_OK,
response_model=list[SChat]
)
async def get_pinned_chats(user: Users = Depends(check_verificated_user_with_exc)):
async def get_pinned_chats(user: SUser = Depends(check_verificated_user_with_exc)):
return await ChatDAO.get_pinned_chats(user_id=user.id)
@ -211,7 +210,7 @@ async def get_pinned_chats(user: Users = Depends(check_verificated_user_with_exc
status_code=status.HTTP_200_OK,
response_model=SPinnedMessage
)
async def pinn_message(chat_id: int, message_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def pinn_message(chat_id: int, message_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
await ChatDAO.pinn_message(chat_id=chat_id, message_id=message_id, user_id=user.id)
return {"message_id": message_id, "user_id": user.id, "chat_id": chat_id}
@ -222,7 +221,7 @@ async def pinn_message(chat_id: int, message_id: int, user: Users = Depends(chec
status_code=status.HTTP_200_OK,
response_model=SPinnedMessage
)
async def unpinn_message(chat_id: int, message_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def unpinn_message(chat_id: int, message_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
message_pinner = await ChatDAO.get_message_pinner(chat_id=chat_id, message_id=message_id)
if message_pinner == user.id:
@ -236,7 +235,7 @@ async def unpinn_message(chat_id: int, message_id: int, user: Users = Depends(ch
status_code=status.HTTP_200_OK,
response_model=list[SMessage] | None
)
async def pinned_messages(chat_id: int, user: Users = Depends(check_verificated_user_with_exc)):
async def pinned_messages(chat_id: int, user: SUser = Depends(check_verificated_user_with_exc)):
await AuthService.validate_user_access_to_chat(chat_id=chat_id, user_id=user.id)
messages = await ChatDAO.get_pinned_messages(chat_id=chat_id)
if messages:

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, UploadFile, HTTPException, status
import httpx
from app.config import settings
from app.images.shemas import SImageUrl, SAvatarUrl
from app.images.shemas import SImageUrl
router = APIRouter(prefix="/images", tags=["Загрузка картинок"])
@ -39,7 +39,7 @@ async def upload_avatar_returning_dict(file: UploadFile, sub_str: str) -> dict:
@router.post(
"/upload_avatar",
status_code=status.HTTP_200_OK,
response_model=SAvatarUrl
response_model=SImageUrl,
)
async def upload_avatar(file: UploadFile):
image_data = await upload_avatar_returning_dict(file, "upload_avatar")
@ -49,21 +49,8 @@ async def upload_avatar(file: UploadFile):
@router.post(
"/upload_image",
status_code=status.HTTP_200_OK,
response_model=SImageUrl
response_model=SImageUrl,
)
async def upload_image(file: UploadFile):
image_url = await upload_file_returning_str(file, "upload_image")
return {"image_url": image_url}
@router.post(
"/upload_background",
status_code=status.HTTP_200_OK,
response_model=list[SImageUrl]
)
async def upload_background(file: UploadFile):
remote_server_url = settings.IMAGE_UPLOAD_SERVER + "upload_background"
response = await upload_file(file, remote_server_url)
uploaded_backgrounds = response.json()
uploaded_backgrounds = [{"image_url": settings.IMAGE_UPLOAD_SERVER + background} for background in uploaded_backgrounds]
return uploaded_backgrounds

View file

@ -1,10 +1,7 @@
from pydantic import BaseModel
class SAvatarUrl(BaseModel):
image_url: str
hex_color: str
from pydantic import BaseModel, HttpUrl
class SImageUrl(BaseModel):
image_url: str
image_url: HttpUrl

View file

@ -1,11 +1,7 @@
from fastapi import FastAPI
from sqladmin import Admin
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from app.admin.auth import authentication_backend
from app.admin.views import UsersAdmin, ChatsAdmin, MessagesAdmin, UsersXChatsAdmin
from app.database import engine
from app.users.router import router as user_router
from app.pages.router import router as pages_router
from app.chat.websocket import router as websocket_router
@ -36,11 +32,4 @@ app.add_middleware(
allow_headers=["Content-Type", "Set-Cookie", "Access-Control-Allow-Headers", "Authorization", "Accept"],
)
admin = Admin(app, engine, authentication_backend=authentication_backend)
admin.add_view(UsersAdmin)
admin.add_view(ChatsAdmin)
admin.add_view(MessagesAdmin)
admin.add_view(UsersXChatsAdmin)
app.mount("/static", StaticFiles(directory="app/static"), name="static")

View file

@ -8,8 +8,8 @@ from alembic import context
sys.path.insert(0, dirname(dirname(abspath(__file__))))
from app.database import DATABASE_URL, Base
from app.users.models import Users, UsersVerificationCodes # noqa
from app.chat.models import Chats, Messages, UsersXChats, PinnedMessages, PinnedChats # noqa
from app.models.chat import Users, UsersVerificationCodes # noqa
from app.models.chat import Chats, Messages, UsersXChats, PinnedMessages, PinnedChats # noqa
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.

View file

@ -7,8 +7,8 @@ from httpx import AsyncClient
from app.config import settings
from app.database import Base, async_session_maker, engine
from app.users.models import Users, UsersVerificationCodes
from app.chat.models import Chats, UsersXChats, Messages
from app.models.chat import Users, UsersVerificationCodes
from app.models.chat import Chats, UsersXChats, Messages
from app.main import app as fastapi_app

View file

@ -13,7 +13,7 @@ from app.exceptions import (
UserMustConfirmEmailException,
)
from app.users.dao import UserDAO
from app.users.models import Users
from app.models.users import Users
from app.users.schemas import SUserRegister
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@ -32,7 +32,7 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def create_access_token(data: dict) -> str:
def create_access_token(data: dict[str, str | datetime]) -> str:
to_encode = data.copy()
expire = datetime.now(UTC) + timedelta(days=30)
to_encode.update({"exp": expire})
@ -83,7 +83,7 @@ class AuthService:
return user.role >= VERIFICATED_USER
@classmethod
async def check_verificated_user_with_exc(cls, user_id: int) -> None:
async def check_verificated_user_with_exc(cls, user_id: int):
if not await cls.check_verificated_user(user_id=user_id):
raise UserMustConfirmEmailException

View file

@ -1,12 +1,10 @@
from typing import Mapping
from sqlalchemy import update, select, insert, and_, func, text, delete
from app.dao.base import BaseDAO
from app.database import async_session_maker, engine # noqa
from app.chat.models import UsersXChats, Chats
from app.users.models import Users, UsersVerificationCodes, UsersAvatars
from app.users.schemas import SUser
from app.models.chat import UsersXChats, Chats
from app.models.users import Users, UsersVerificationCodes, UsersAvatars
from app.users.schemas import SUser, SUserAvatars
class UserDAO(BaseDAO):
@ -117,12 +115,24 @@ class UserDAO(BaseDAO):
return True
@staticmethod
async def get_user_avatars(user_id: int) -> list[Mapping[str, str]]:
query = select(UsersAvatars.avatar_image).where(UsersAvatars.user_id == user_id)
async def get_user_avatars(user_id: int) -> SUserAvatars:
query = select(
func.json_build_object(
"user_avatars", select(
func.json_agg(
func.json_build_object(
"avatar_url", UsersAvatars.avatar_image
)
)
)
.where(UsersAvatars.user_id == user_id)
.scalar_subquery()
)
)
async with async_session_maker() as session:
result = await session.execute(query)
result = result.mappings().all()
return result
result = result.scalar()
return SUserAvatars.model_validate(result)
@staticmethod
async def delete_user_avatar(avatar_id: int, user_id: int) -> bool:

View file

@ -1,5 +1,3 @@
from datetime import datetime, UTC
from fastapi import Depends, Request, Response, WebSocket
from jose import JWTError, jwt, ExpiredSignatureError
@ -39,7 +37,7 @@ async def get_current_user(response: Response, token: str = Depends(get_token))
if not user:
raise UserIsNotPresentException
access_token = create_access_token({"sub": user.id})
access_token = create_access_token({"sub": str(user.id)})
response.set_cookie(key="black_phoenix_access_token", value=access_token, httponly=True)
return user

View file

@ -14,7 +14,6 @@ from app.exceptions import (
from app.users.auth import get_password_hash, create_access_token, VERIFICATED_USER, AuthService
from app.users.dao import UserDAO, UserCodesDAO
from app.users.dependencies import get_current_user
from app.users.models import Users
from app.users.schemas import (
SUserLogin,
SUserRegister,
@ -29,7 +28,7 @@ from app.users.schemas import (
SPasswordRecovered,
SUserAvatars,
SUsername,
SEmail,
SEmail, SUser,
)
from app.tasks.tasks import send_registration_confirmation_email, send_password_change_email, \
send_password_recover_email
@ -40,7 +39,7 @@ router = APIRouter(prefix="/users", tags=["Пользователи"])
@router.get(
"",
status_code=status.HTTP_200_OK,
response_model=list[SUserResponse]
response_model=list[SUserResponse],
)
async def get_all_users():
users = await UserDAO.find_all()
@ -50,7 +49,7 @@ async def get_all_users():
@router.post(
"/check_existing_username",
status_code=status.HTTP_200_OK,
response_model=None
response_model=None,
)
async def check_existing_username(username: SUsername):
user = await UserDAO.find_one_or_none(username=username.username)
@ -61,7 +60,7 @@ async def check_existing_username(username: SUsername):
@router.post(
"/check_existing_email",
status_code=status.HTTP_200_OK,
response_model=None
response_model=None,
)
async def check_existing_email(email: SEmail):
user = await UserDAO.find_one_or_none(email=email.email)
@ -102,7 +101,7 @@ async def register_user(response: Response, user_data: SUserRegister):
@router.get(
"/email_verification/{user_code}",
status_code=status.HTTP_200_OK,
response_model=SEmailVerification
response_model=SEmailVerification,
)
async def email_verification(user_code: str):
invitation_token = user_code.encode()
@ -120,7 +119,7 @@ async def email_verification(user_code: str):
@router.post(
"/login",
status_code=status.HTTP_200_OK,
response_model=SUserToken
response_model=SUserToken,
)
async def login_user(response: Response, user_data: SUserLogin):
user = await AuthService.authenticate_user(user_data.email_or_username, user_data.password)
@ -132,7 +131,7 @@ async def login_user(response: Response, user_data: SUserLogin):
@router.post(
"/logout",
status_code=status.HTTP_200_OK,
response_model=None
response_model=None,
)
async def logout_user(response: Response):
response.delete_cookie("black_phoenix_access_token")
@ -141,16 +140,16 @@ async def logout_user(response: Response):
@router.get(
"/me",
status_code=status.HTTP_200_OK,
response_model=SUserResponse
response_model=SUserResponse,
)
async def get_user(current_user: Users = Depends(get_current_user)):
async def get_user(current_user: SUser = Depends(get_current_user)):
return current_user
@router.patch(
"/send_recovery_email",
status_code=status.HTTP_200_OK,
response_model=SRecoverEmailSent
response_model=SRecoverEmailSent,
)
async def send_recovery_email(email: SUserPasswordRecover):
existing_user = await UserDAO.find_one_or_none(email=email.email)
@ -172,7 +171,7 @@ async def send_recovery_email(email: SUserPasswordRecover):
@router.post(
"/confirm_password_recovery",
status_code=status.HTTP_200_OK,
response_model=SConfirmPasswordRecovery
response_model=SConfirmPasswordRecovery,
)
async def confirm_password_recovery(user_code: SUserCode):
user_codes = await UserCodesDAO.get_user_codes(description="Код восстановления пароля", code=user_code.user_code)
@ -184,7 +183,7 @@ async def confirm_password_recovery(user_code: SUserCode):
@router.post(
"/password_recovery",
status_code=status.HTTP_200_OK,
response_model=SPasswordRecovered
response_model=SPasswordRecovered,
)
async def password_recovery(passwords: SUserPasswordChange):
if passwords.password1 != passwords.password2:
@ -201,7 +200,7 @@ async def password_recovery(passwords: SUserPasswordChange):
@router.get(
"/avatars",
status_code=status.HTTP_200_OK,
response_model=list[SUserAvatars]
response_model=SUserAvatars,
)
async def get_user_avatars_history(user: Users = Depends(get_current_user)):
async def get_user_avatars_history(user: SUser = Depends(get_current_user)):
return await UserDAO.get_user_avatars(user_id=user.id)

View file

@ -40,7 +40,7 @@ class SUserResponse(BaseModel):
id: int
username: str
black_phoenix: bool
avatar_image: str
avatar_image: HttpUrl
avatar_hex: str
date_of_birth: date
date_of_registration: date
@ -53,7 +53,7 @@ class SUser(BaseModel):
hashed_password: str
role: int
black_phoenix: bool
avatar_image: str
avatar_image: HttpUrl
avatar_hex: str
date_of_birth: date
date_of_registration: date
@ -66,7 +66,7 @@ class SUserRename(BaseModel):
class SUserAvatar(BaseModel):
password: str
new_avatar_image: str
new_avatar_image: HttpUrl
avatar_hex: str
@ -107,7 +107,7 @@ class SUserName(BaseModel):
class SNewAvatar(BaseModel):
new_avatar_image: str
new_avatar_image: HttpUrl
avatar_hex: str
@ -120,15 +120,19 @@ class SPasswordRecovered(BaseModel):
class SCreateInvitationLink(BaseModel):
invitation_link: str
invitation_link: HttpUrl
class SUserAddedToChat(BaseModel):
user_added_to_chat: bool
class SUserAvatarUrl(BaseModel):
avatar_url: HttpUrl
class SUserAvatars(BaseModel):
avatar_image: HttpUrl
user_avatars: list[SUserAvatarUrl] | None
class SUsername(BaseModel):