from fastapi import Depends, APIRouter, HTTPException, Depends, Request from sqlalchemy.orm import Session from app.db import models from app.core.security import get_current_user from app.api import schemas from sqlalchemy import or_, and_, exists from sqlalchemy.exc import IntegrityError from app.websocket import connection_manager # бд def get_db(): db = models.SessionLocal() try: yield db finally: db.close() usersRouter = APIRouter( prefix="/users", tags=[], ) # Пример защищенного роута @usersRouter.get("/me") async def read_users_me(current_user: models.User = Depends(get_current_user)): return { "id": current_user.id, "username": current_user.username, "first_name": current_user.first_name, "last_name": current_user.last_name, "phone": current_user.phone, "email": getattr(current_user, "email", None), "about": current_user.about, "public_key": current_user.public_key, "encrypted_private_key": current_user.encrypted_private_key, "avatar_file_id": current_user.avatar_file_id, "totp_enabled": bool(current_user.totp_secret != None), } @usersRouter.put("/me") async def update_users_me( data: schemas.UpdateMe, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db), ): user_to_update = db.merge(current_user) if data.username is not None: user_to_update.username = data.username if data.first_name is not None: user_to_update.first_name = data.first_name if data.last_name is not None: user_to_update.last_name = data.last_name if data.phone is not None: user_to_update.phone = data.phone or None if data.email is not None: user_to_update.email = data.email or None if data.about is not None: user_to_update.about = data.about or None try: db.commit() except IntegrityError: db.rollback() raise HTTPException( status_code=400, detail="phone/email already in use") db.refresh(user_to_update) await connection_manager.manager.broadcast({'type': 'user_updated', 'user_id': current_user.id}) return { "status": "ok", "user": { "id": user_to_update.id, "username": user_to_update.username, "first_name": user_to_update.first_name, "last_name": user_to_update.last_name, "phone": user_to_update.phone, "email": getattr(user_to_update, "email", None), "about": user_to_update.about, }, } @usersRouter.put("/me/encryption-key") async def update_encrypted_private_key( data: schemas.UpdateEncryptedPrivateKey, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db), ): user_to_update = db.merge(current_user) user_to_update.encrypted_private_key = data.encrypted_private_key try: db.commit() except Exception: db.rollback() raise HTTPException( status_code=500, detail="Не удалось сохранить ключ шифрования") db.refresh(user_to_update) await connection_manager.manager.broadcast({'type': 'user_updated', 'user_id': current_user.id}) return {"status": "ok"} @usersRouter.put("/me/password") async def change_password( data: schemas.ChangePassword, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db), ): from app.core.security import verify_password, get_password_hash if not verify_password(data.current_password, current_user.hashed_password): raise HTTPException(status_code=400, detail="Неверный текущий пароль") user_to_update = db.merge(current_user) user_to_update.hashed_password = get_password_hash(data.new_password) try: db.commit() except Exception: db.rollback() raise HTTPException( status_code=500, detail="Не удалось изменить пароль") db.refresh(user_to_update) return {"status": "ok"} @usersRouter.put("/me/privacy") async def update_privacy_settings( data: schemas.UpdatePrivacySettings, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db), ): user_to_update = db.merge(current_user) if data.show_email is not None: user_to_update.show_email = 1 if data.show_email else 0 if data.show_phone is not None: user_to_update.show_phone = 1 if data.show_phone else 0 if data.show_avatar is not None: user_to_update.show_avatar = 1 if data.show_avatar else 0 if data.show_about is not None: user_to_update.show_about = 1 if data.show_about else 0 if data.show_username is not None: user_to_update.show_username = 1 if data.show_username else 0 if data.show_last_online is not None: user_to_update.show_last_online = 1 if data.show_last_online else 0 try: db.commit() except Exception: db.rollback() raise HTTPException( status_code=500, detail="Не удалось сохранить настройки конфиденциальности") db.refresh(user_to_update) await connection_manager.manager.broadcast({'type': 'user_updated', 'user_id': current_user.id}) return {"status": "ok"} @usersRouter.get("/me/privacy") async def get_privacy_settings(current_user: models.User = Depends(get_current_user)): """ Получить настройки конфиденциальности текущего пользователя. """ return { "show_email": bool(current_user.show_email), "show_phone": bool(current_user.show_phone), "show_avatar": bool(current_user.show_avatar), "show_about": bool(current_user.show_about), "show_username": bool(current_user.show_username), "show_last_online": bool(current_user.show_last_online), } @usersRouter.get("/all") async def read_users_all(current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)): users = db.query(models.User).all() return [{"id": user.id, "username": user.username, "name": f"{user.first_name} {user.last_name or ''}".strip(), "public_key": user.public_key} for user in users] @usersRouter.get("/chats") async def read_users_chats( request: Request, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db), ): """ Список контактов для экрана чатов: последний месседж + время + непрочитанные. last_message возвращается в том виде, как хранится в БД (зашифрованный content). Клиент должен расшифровать превью локально. """ users = ( db.query(models.User) .filter(models.User.id != current_user.id) .filter(exists().where( or_( and_(models.Message.sender_id == current_user.id, models.Message.receiver_id == models.User.id), and_(models.Message.sender_id == models.User.id, models.Message.receiver_id == current_user.id) ) )) .all() ) result = [] for user in users: last_msg = ( db.query(models.Message) .filter( or_( and_( models.Message.sender_id == current_user.id, models.Message.receiver_id == user.id, ), and_( models.Message.sender_id == user.id, models.Message.receiver_id == current_user.id, ), ) ) .order_by(models.Message.timestamp.desc()) .first() ) unread_count = ( db.query(models.Message) .filter( models.Message.sender_id == user.id, models.Message.receiver_id == current_user.id, models.Message.read_at.is_(None), ) .count() ) result.append( { "id": user.id, "username": user.username, "name": f"{user.first_name} {user.last_name or ''}".strip(), "public_key": user.public_key, "avatar_file_id": user.avatar_file_id, "avatar_url": str(request.url_for("get_file", file_id=user.avatar_file_id)) if user.show_avatar and user.avatar_file_id else None, "last_message": last_msg.content if last_msg else None, "last_message_time": (last_msg.timestamp.isoformat() if last_msg and last_msg.timestamp else None), "unread_count": unread_count, } ) result.sort(key=lambda x: x['last_message_time'] or '', reverse=True) return result @usersRouter.get("/{user_id}", response_model=schemas.UserProfile) def get_user_by_id( user_id: int, request: Request, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user) ): """ Получить информацию о пользователе с учетом настроек конфиденциальности. """ user = db.query(models.User).filter(models.User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь не найден") # Возвращаем информацию с учетом настроек конфиденциальности profile_data = { "id": user.id, "public_key": user.public_key, } profile_data["first_name"] = user.first_name profile_data["last_name"] = user.last_name # Проверяем настройки конфиденциальности if user.show_username: profile_data["username"] = user.username if user.show_avatar: profile_data["avatar_url"] = str(request.url_for( "get_file", file_id=user.avatar_file_id)) if user.avatar_file_id else None profile_data["show_avatar"] = bool(user.show_avatar) profile_data["totp_enabled"] = bool(user.totp_secret) if user.show_about: profile_data["about"] = user.about if user.show_phone: profile_data["phone"] = user.phone if user.show_email: profile_data["email"] = user.email if str(user.id) in connection_manager.manager.active_connections: profile_data["online"] = True else: profile_data["online"] = False if user.show_last_online: profile_data["last_online"] = user.last_online.isoformat( ) if user.last_online else None return profile_data @usersRouter.put("/me/avatar") async def update_user_avatar( data: dict, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db), ): user_to_update = db.merge(current_user) avatar_file_id = data.get("avatar_file_id") if avatar_file_id: user_to_update.avatar_file_id = avatar_file_id db.commit() print( f"Пользователь {user_to_update.id} обновил аватар: {avatar_file_id}") else: raise HTTPException( status_code=400, detail="avatar_file_id is required") await connection_manager.manager.broadcast({'type': 'user_updated', 'user_id': current_user.id}) return {"message": "Avatar updated"}