diff --git a/backend/app/auth/router.py b/backend/app/auth/router.py index 10a3f75..9f328be 100644 --- a/backend/app/auth/router.py +++ b/backend/app/auth/router.py @@ -58,6 +58,27 @@ async def login(response: Response, credentials: OAuth2PasswordRequestForm = Dep ) return token +@router.post("/guest") +async def guest_login(response: Response) -> Token: + user = await UserService.create_guest_user() + token = await AuthService.create_token(user.id) + + response.set_cookie( + 'access_token', + token.access_token, + max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60, + httponly=True, + samesite='lax' + ) + response.set_cookie( + 'refresh_token', + str(token.refresh_token), + max_age=settings.REFRESH_TOKEN_EXPIRE_DAYS * 30 * 24 * 60, + httponly=True, + samesite='lax' + ) + return token + @router.post("/refresh") async def refresh_token(request: Request, response: Response) -> Token: new_token = await AuthService.refresh_token(uuid.UUID(request.cookies.get("refresh_token"))) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 0dc6b82..60af26b 100755 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -19,6 +19,11 @@ class Settings(BaseSettings): FIRST_SUPER_USER_PASS: str FIRST_SUPER_USER_USERNAME: str + GUEST_USER_EMAIL: str = "guest@example.com" + GUEST_USER_USERNAME: str = "guest" + GUEST_USER_DISPLAY_NAME: str = "Гость" + GUEST_USER_PASSWORD: str = "guest" + CORS_ORIGINS: List[str] = ["http://localhost:5500", "http://127.0.0.1:5500", "http://localhost:8080", "http://127.0.0.1:8080", "null"] CORS_HEADERS: List[str] = ["*"] CORS_METHODS: List[str] = ["*"] diff --git a/backend/app/users/service.py b/backend/app/users/service.py index 14f55c1..6148ff5 100644 --- a/backend/app/users/service.py +++ b/backend/app/users/service.py @@ -22,6 +22,31 @@ log = logging.getLogger(__name__) class UserService: @classmethod + async def create_guest_user(cls) -> UserModel: + async with async_session_maker() as session: + unique = uuid.uuid4().hex[:8] + username_prefix = settings.GUEST_USER_USERNAME or "guest" + email_base = settings.GUEST_USER_EMAIL or "guest@example.com" + if "@" in email_base: + _, domain = email_base.split("@", 1) + else: + domain = "example.com" + + user_db = await UserDAO.add( + session, + UserCreateDB( + display_name=f"{settings.GUEST_USER_DISPLAY_NAME} #{unique[:4]}", + username=f"{username_prefix}_{unique}", + email=f"{username_prefix}_{unique}@{domain}", + hashed_password=hash_password(uuid.uuid4().hex), + is_active=True, + is_verified=True, + is_superuser=False + ) + ) + await session.commit() + return user_db + @classmethod async def get_user(cls, user_id: int) -> User: async with async_session_maker() as session: user_exist = await UserDAO.find_one_or_none(session, id=user_id) diff --git a/frontend/src/pages/AuthPage.tsx b/frontend/src/pages/AuthPage.tsx index 349593c..497e52c 100644 --- a/frontend/src/pages/AuthPage.tsx +++ b/frontend/src/pages/AuthPage.tsx @@ -1,11 +1,33 @@ import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import LoginForm from '../components/auth/LoginForm'; import RegisterForm from '../components/auth/RegisterForm'; import miniLogo from '../assets/mini-logo.png'; +import { authService } from '../services/authService'; +import { useAuthStore } from '../store/authStore'; export default function AuthPage() { const [isLogin, setIsLogin] = useState(true); + const [guestError, setGuestError] = useState(''); + const [isGuestLoading, setIsGuestLoading] = useState(false); + const navigate = useNavigate(); + const setUser = useAuthStore((state) => state.setUser); + + const handleGuestLogin = async () => { + setGuestError(''); + setIsGuestLoading(true); + try { + await authService.guestLogin(); + const user = await authService.getCurrentUser(); + setUser(user); + navigate('/chat'); + } catch (err: any) { + setGuestError(err.response?.data?.detail || 'Не удалось войти как гость'); + } finally { + setIsGuestLoading(false); + } + }; return (
@@ -59,6 +81,28 @@ export default function AuthPage() { +
+ {guestError && ( + + {guestError} + + )} + + {isGuestLoading ? 'Вход...' : 'Войти как гость'} + +
+ {/* Switch */}
{isLogin ? ( diff --git a/frontend/src/pages/ChatPage.tsx b/frontend/src/pages/ChatPage.tsx index 0f546f3..36f2261 100644 --- a/frontend/src/pages/ChatPage.tsx +++ b/frontend/src/pages/ChatPage.tsx @@ -182,16 +182,57 @@ export default function ChatPage() { } // Update chat list with new last message - setChats(prevChats => - prevChats.map(chat => + setChats(prevChats => { + const existingIndex = prevChats.findIndex(chat => chat.chat_id === message.chat_id); + + if (existingIndex === -1) { + const otherUserId = message.sender_id === user?.id ? selectedChat?.user_id : message.sender_id; + const newChat: Chat = { + chat_id: message.chat_id, + user_id: otherUserId || 0, + last_message: message.content, + avatar_url: null, + display_name: 'Новый чат' + }; + + const updated = [newChat, ...prevChats]; + setChatsCache(updated); + + if (otherUserId) { + userService.getUserById(otherUserId) + .then((userData) => { + setChats(current => { + const hydrated = current.map(chat => + chat.chat_id === message.chat_id + ? { + ...chat, + user_id: userData.id, + display_name: userData.display_name || userData.username, + avatar_url: userData.avatar_url || null + } + : chat + ); + setChatsCache(hydrated); + return hydrated; + }); + }) + .catch((err) => { + console.error('Failed to load chat user:', err); + }); + } + + return updated; + } + + const updated = prevChats.map(chat => chat.chat_id === message.chat_id ? { ...chat, last_message: message.content } : chat - ) - ); - - // Update cache - updateChatCache(message.chat_id, { last_message: message.content }); + ); + setChatsCache(updated); + updateChatCache(message.chat_id, { last_message: message.content }); + return updated; + }); } catch (error) { console.error('Error parsing WebSocket message:', error); } diff --git a/frontend/src/services/authService.ts b/frontend/src/services/authService.ts index 7238d97..d2171eb 100644 --- a/frontend/src/services/authService.ts +++ b/frontend/src/services/authService.ts @@ -27,6 +27,11 @@ export const authService = { return response.data; }, + guestLogin: async () => { + const response = await apiClient.post('/auth/guest'); + return response.data; + }, + register: async (data: RegisterData) => { const response = await apiClient.post('/auth/register', data); return response.data;