from fastapi import FastAPI, HTTPException, Depends, APIRouter, Response, Header, File, UploadFile, Request from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from pydantic import BaseModel, EmailStr from sqlalchemy.orm import Session from sqlalchemy.exc import IntegrityError from urllib.parse import quote, unquote from core.db import get_db, User from core.crypt import hash_password, verify_password, gen_jwt, decode_jwt from core.file import get_upload_dir, save_file, generate_file_url import os from pathlib import Path from datetime import datetime router = APIRouter() security = HTTPBearer() class RegisterRequest(BaseModel): login: str password: str email: EmailStr last_name: str first_name: str middle_name: str | None = None @router.post("/register") def register_user(user_data: RegisterRequest, db: Session = Depends(get_db)): existing_user = db.query(User).filter(User.login == user_data.login).first() if existing_user: raise HTTPException(status_code=400, detail="Пользователь с таким логином уже существует.") existing_email = db.query(User).filter(User.email == user_data.email).first() if existing_email: raise HTTPException(status_code=400, detail="Пользователь с такой почтой уже существует.") hashed_password = hash_password(user_data.password) new_user = User( login=user_data.login, password=hashed_password, email=user_data.email, last_name=user_data.last_name, first_name=user_data.first_name, middle_name=user_data.middle_name, access_level=1, ) try: db.add(new_user) db.commit() db.refresh(new_user) except IntegrityError: db.rollback() raise HTTPException(status_code=500, detail="Ошибка при добавлении пользователя в базу данных.") return {"message": "Пользователь успешно зарегистрирован."} class LoginRequest(BaseModel): login: str password: str @router.post("/login") def login_user(user_data: LoginRequest, db: Session = Depends(get_db), response: Response = None): user = db.query(User).filter(User.login == user_data.login).first() if not user: raise HTTPException(status_code=404, detail="Пользователя с таким логином не существует.") if not verify_password(user.password, user_data.password): raise HTTPException(status_code=401, detail="Неверный пароль.") JWT_token = gen_jwt(user.id, user.access_level) try: if response: cookie_params = { "httponly": False, "samesite": "lax", "max_age": 3600*24*7, "path": "/", } response.set_cookie(key="id", value=str(user.id), **cookie_params) response.set_cookie(key="login", value=quote(user.login), **cookie_params) response.set_cookie(key="email", value=quote(user.email), **cookie_params) full_name = f"{user.last_name} {user.first_name} {user.middle_name or ''}".strip() response.set_cookie(key="full_name", value=quote(full_name), **cookie_params) avatar_value = quote(user.avatar) if user.avatar else quote("/default-avatar.png") response.set_cookie(key="avatar", value=avatar_value, **cookie_params) response.set_cookie(key="JWT_token", value=JWT_token, **cookie_params) print(f"JWT токен установлен для пользователя {user.login}: {JWT_token[:10]}...") except Exception as e: print(f"Ошибка при установке cookie: {str(e)}") return { "message": "Успешный вход.", "token": JWT_token, "user_id": user.id, "login": user.login } @router.post("/logout") def logout_user(response: Response): response.delete_cookie(key="login") response.delete_cookie(key="email") response.delete_cookie(key="full_name") response.delete_cookie(key="id") response.delete_cookie(key="avatar") response.delete_cookie(key="JWT_token") return {"message": "Вы успешно вышли из системы."} @router.get("/user/{user_id}") def get_user_data(user_id: int, db: Session = Depends(get_db)): user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь с таким ID не найден.") return { "login": user.login, "avatar": user.avatar, "first_name": user.first_name, "last_name": user.last_name, "middle_name": user.middle_name, "email": user.email } class ChangePasswordRequest(BaseModel): old_password: str new_password: str @router.post("/change_password") def change_password( password_data: ChangePasswordRequest, credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db) ): token = credentials.credentials decoded_data = decode_jwt(token) user_id = decoded_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="Недействительный токен.") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь не найден.") if not verify_password(user.password, password_data.old_password): raise HTTPException(status_code=401, detail="Неверный старый пароль.") user.password = hash_password(password_data.new_password) try: db.commit() except: db.rollback() raise HTTPException(status_code=500, detail="Ошибка при обновлении пароля.") return {"message": "Пароль успешно изменён."} class ChangeUsernameRequest(BaseModel): new_login: str @router.post("/change_username") def change_username( login_data: ChangeUsernameRequest, credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), response: Response = None ): token = credentials.credentials decoded_data = decode_jwt(token) user_id = decoded_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="Недействительный токен.") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь не найден.") existing_user = db.query(User).filter(User.login == login_data.new_login).first() if existing_user: raise HTTPException(status_code=400, detail="Логин уже используется другим пользователем.") try: user.login = login_data.new_login db.commit() db.refresh(user) # Безопасно устанавливаем cookie try: if response: response.set_cookie( key="login", value=user.login, httponly=False ) except: # Игнорируем ошибку установки cookie pass except Exception as e: db.rollback() raise HTTPException(status_code=500, detail="Ошибка при обновлении логина.") return {"message": "Логин успешно изменен."} class ChangeFirstNameRequest(BaseModel): new_first_name: str @router.post("/change_name") def change_first_name( name_data: ChangeFirstNameRequest, credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), response: Response = None ): token = credentials.credentials decoded_data = decode_jwt(token) user_id = decoded_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="Недействительный токен.") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь с таким ID не найден.") try: user.first_name = name_data.new_first_name db.commit() db.refresh(user) # Безопасная установка cookie с проверкой response и URL-кодированием try: if response: full_name = f"{user.last_name} {user.first_name} {user.middle_name or ''}".strip() response.set_cookie( key="full_name", value=quote(full_name), # URL-кодирование для поддержки кириллицы httponly=False, samesite="lax", max_age=3600*24*30, # 30 дней path="/" ) except Exception as e: print(f"Ошибка при установке cookie: {str(e)}") # Продолжаем выполнение даже при ошибке cookie except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"Ошибка при обновлении имени: {str(e)}") # Возвращаем обновленные данные для использования на клиенте return { "message": "Имя успешно изменено.", "full_name": f"{user.last_name} {user.first_name} {user.middle_name or ''}".strip(), "updated_at": str(datetime.now()) } class ChangeLastNameRequest(BaseModel): new_last_name: str @router.post("/change_last_name") def change_last_name( last_name_data: ChangeLastNameRequest, credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), response: Response = None ): token = credentials.credentials decoded_data = decode_jwt(token) user_id = decoded_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="Недействительный токен.") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь с таким ID не найден.") try: user.last_name = last_name_data.new_last_name db.commit() db.refresh(user) # Безопасно устанавливаем cookie try: if response: response.set_cookie( key="full_name", value=f"{user.last_name} {user.first_name} {user.middle_name or ''}", httponly=False ) except: # Игнорируем ошибку установки cookie pass except Exception as e: db.rollback() raise HTTPException(status_code=500, detail="Ошибка при обновлении фамилии.") return {"message": "Фамилия успешно изменена."} class ChangeMiddleNameRequest(BaseModel): new_middle_name: str | None = None @router.post("/change_middle_name") def change_middle_name( middle_name_data: ChangeMiddleNameRequest, credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), response: Response = None ): token = credentials.credentials decoded_data = decode_jwt(token) user_id = decoded_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="Недействительный токен.") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь с таким ID не найден.") try: user.middle_name = middle_name_data.new_middle_name db.commit() db.refresh(user) # Безопасно устанавливаем cookie try: if response: response.set_cookie( key="full_name", value=f"{user.last_name} {user.first_name} {user.middle_name or ''}", httponly=False ) except: # Игнорируем ошибку установки cookie pass except Exception as e: db.rollback() raise HTTPException(status_code=500, detail="Ошибка при обновлении отчества.") return {"message": "Отчество успешно изменено."} class ChangeEmailRequest(BaseModel): new_email: EmailStr @router.post("/change_email") def change_email( email_data: ChangeEmailRequest, credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), response: Response = None ): token = credentials.credentials decoded_data = decode_jwt(token) user_id = decoded_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="Недействительный токен.") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь с таким ID не найден.") existing_user = db.query(User).filter(User.email == email_data.new_email).first() if existing_user: raise HTTPException(status_code=400, detail="Почта уже используется другим пользователем.") try: user.email = email_data.new_email db.commit() db.refresh(user) # Безопасно устанавливаем cookie try: if response: response.set_cookie( key="email", value=user.email, httponly=False ) except: # Игнорируем ошибку установки cookie pass except Exception as e: db.rollback() raise HTTPException(status_code=500, detail="Ошибка при обновлении почты.") return {"message": "Почта успешно изменена."} @router.post("/upload_avatar") def upload_avatar( file: UploadFile = File(...), credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), request: Request = None, response: Response = None ): token = credentials.credentials decoded_data = decode_jwt(token) user_id = decoded_data.get("user_id") if not user_id: raise HTTPException(status_code=401, detail="Недействительный токен.") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь с таким ID не найден.") if not file.content_type.startswith("image/"): raise HTTPException(status_code=400, detail="Файл должен быть изображением.") try: upload_dir = get_upload_dir(user_id) file_path = save_file(file, upload_dir) file_url = generate_file_url(request, file_path) user.avatar = str(file_url) db.commit() db.refresh(user) # Безопасно устанавливаем cookie try: if response: response.set_cookie( key="avatar", value=file_url, httponly=False ) except: # Игнорируем ошибку установки cookie pass except Exception as e: db.rollback() raise HTTPException(status_code=500, detail=f"Ошибка при загрузке файла: {str(e)}") return {"message": "Аватар успешно загружен.", "file_url": file_url}