Chepuhagram/srv/app/api/endpoints/users.py

339 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"}