mirror of
https://github.com/lorsanstand/Aether.git
synced 2026-06-19 12:05:16 +03:00
@@ -58,6 +58,27 @@ async def login(response: Response, credentials: OAuth2PasswordRequestForm = Dep
|
|||||||
)
|
)
|
||||||
return token
|
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")
|
@router.post("/refresh")
|
||||||
async def refresh_token(request: Request, response: Response) -> Token:
|
async def refresh_token(request: Request, response: Response) -> Token:
|
||||||
new_token = await AuthService.refresh_token(uuid.UUID(request.cookies.get("refresh_token")))
|
new_token = await AuthService.refresh_token(uuid.UUID(request.cookies.get("refresh_token")))
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ class Settings(BaseSettings):
|
|||||||
FIRST_SUPER_USER_PASS: str
|
FIRST_SUPER_USER_PASS: str
|
||||||
FIRST_SUPER_USER_USERNAME: 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_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_HEADERS: List[str] = ["*"]
|
||||||
CORS_METHODS: List[str] = ["*"]
|
CORS_METHODS: List[str] = ["*"]
|
||||||
|
|||||||
@@ -22,6 +22,31 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class UserService:
|
class UserService:
|
||||||
@classmethod
|
@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 def get_user(cls, user_id: int) -> User:
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
user_exist = await UserDAO.find_one_or_none(session, id=user_id)
|
user_exist = await UserDAO.find_one_or_none(session, id=user_id)
|
||||||
|
|||||||
@@ -1,11 +1,33 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import LoginForm from '../components/auth/LoginForm';
|
import LoginForm from '../components/auth/LoginForm';
|
||||||
import RegisterForm from '../components/auth/RegisterForm';
|
import RegisterForm from '../components/auth/RegisterForm';
|
||||||
import miniLogo from '../assets/mini-logo.png';
|
import miniLogo from '../assets/mini-logo.png';
|
||||||
|
import { authService } from '../services/authService';
|
||||||
|
import { useAuthStore } from '../store/authStore';
|
||||||
|
|
||||||
export default function AuthPage() {
|
export default function AuthPage() {
|
||||||
const [isLogin, setIsLogin] = useState(true);
|
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 (
|
return (
|
||||||
<div className="w-screen h-screen flex items-center justify-center p-3 md:p-4 relative overflow-hidden" style={{ backgroundColor: '#F5F5F1' }}>
|
<div className="w-screen h-screen flex items-center justify-center p-3 md:p-4 relative overflow-hidden" style={{ backgroundColor: '#F5F5F1' }}>
|
||||||
@@ -59,6 +81,28 @@ export default function AuthPage() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<div className="mt-5 md:mt-6">
|
||||||
|
{guestError && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="p-3 mb-4 bg-error-soft/10 border-b-2 border-error-soft text-error-soft text-sm font-inter"
|
||||||
|
>
|
||||||
|
{guestError}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
<motion.button
|
||||||
|
type="button"
|
||||||
|
disabled={isGuestLoading}
|
||||||
|
onClick={handleGuestLogin}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="w-full py-[14px] px-8 rounded-full font-inter font-semibold uppercase tracking-wider border-2 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
style={{ borderColor: '#6B705C', color: '#6B705C' }}
|
||||||
|
>
|
||||||
|
{isGuestLoading ? 'Вход...' : 'Войти как гость'}
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Switch */}
|
{/* Switch */}
|
||||||
<div className="mt-5 md:mt-6 text-center text-xs md:text-sm font-inter" style={{ color: '#8B8B8B' }}>
|
<div className="mt-5 md:mt-6 text-center text-xs md:text-sm font-inter" style={{ color: '#8B8B8B' }}>
|
||||||
{isLogin ? (
|
{isLogin ? (
|
||||||
|
|||||||
@@ -182,16 +182,57 @@ export default function ChatPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update chat list with new last message
|
// Update chat list with new last message
|
||||||
setChats(prevChats =>
|
setChats(prevChats => {
|
||||||
prevChats.map(chat =>
|
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.chat_id === message.chat_id
|
||||||
? { ...chat, last_message: message.content }
|
? { ...chat, last_message: message.content }
|
||||||
: chat
|
: chat
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
setChatsCache(updated);
|
||||||
// Update cache
|
|
||||||
updateChatCache(message.chat_id, { last_message: message.content });
|
updateChatCache(message.chat_id, { last_message: message.content });
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing WebSocket message:', error);
|
console.error('Error parsing WebSocket message:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ export const authService = {
|
|||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
guestLogin: async () => {
|
||||||
|
const response = await apiClient.post('/auth/guest');
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
register: async (data: RegisterData) => {
|
register: async (data: RegisterData) => {
|
||||||
const response = await apiClient.post('/auth/register', data);
|
const response = await apiClient.post('/auth/register', data);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|||||||
Reference in New Issue
Block a user