339 lines
11 KiB
Python
339 lines
11 KiB
Python
|
||
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"}
|