feat: Implementar funcionalidades de Tarefas e Saúde
- Criadas APIs para Health (métricas de saúde) * Registrar peso, altura, % gordura, medidas * Histórico completo de medições * Estatísticas e resumo - Criadas APIs para Tasks (tarefas) * Criar, editar e deletar tarefas * Filtros por status e data * Estatísticas detalhadas * Prioridades (baixa, média, alta) - Frontend implementado: * Página Health.tsx - registro de métricas * Página Tasks.tsx - gerenciamento de tarefas * Página Progress.tsx - visualização de progresso * Dashboard integrado com estatísticas reais - Schemas e modelos atualizados - Todas as funcionalidades testadas e operacionais
This commit is contained in:
148
backend/app/api/admin.py
Normal file
148
backend/app/api/admin.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import decode_access_token, get_password_hash
|
||||
from app.models.user import User
|
||||
from app.schemas.admin import UserListResponse, UserUpdateRequest, PasswordChangeRequest
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
security = HTTPBearer()
|
||||
|
||||
def get_current_superadmin(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: Session = Depends(get_db)
|
||||
) -> User:
|
||||
"""Verifica se usuário é superadmin"""
|
||||
token = credentials.credentials
|
||||
payload = decode_access_token(token)
|
||||
if not payload:
|
||||
raise HTTPException(status_code=401, detail="Token inválido")
|
||||
|
||||
user_id = payload.get("sub")
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
|
||||
if not user or not user.is_superadmin:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Acesso negado. Apenas superadmin."
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
@router.get("/users", response_model=List[UserListResponse])
|
||||
async def list_all_users(
|
||||
admin: User = Depends(get_current_superadmin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Listar todos os usuários (apenas superadmin)"""
|
||||
users = db.query(User).order_by(User.created_at.desc()).all()
|
||||
return users
|
||||
|
||||
@router.get("/stats")
|
||||
async def get_admin_stats(
|
||||
admin: User = Depends(get_current_superadmin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Estatísticas gerais do sistema"""
|
||||
from sqlalchemy import func
|
||||
from app.models.habit import Habit, HabitCompletion
|
||||
|
||||
total_users = db.query(func.count(User.id)).scalar()
|
||||
active_users = db.query(func.count(User.id)).filter(User.is_active == True).scalar()
|
||||
total_habits = db.query(func.count(Habit.id)).scalar()
|
||||
total_completions = db.query(func.count(HabitCompletion.id)).scalar()
|
||||
|
||||
return {
|
||||
"total_users": total_users,
|
||||
"active_users": active_users,
|
||||
"inactive_users": total_users - active_users,
|
||||
"total_habits": total_habits,
|
||||
"total_completions": total_completions
|
||||
}
|
||||
|
||||
@router.patch("/users/{user_id}")
|
||||
async def update_user(
|
||||
user_id: str,
|
||||
data: UserUpdateRequest,
|
||||
admin: User = Depends(get_current_superadmin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Atualizar dados de usuário"""
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Usuário não encontrado")
|
||||
|
||||
# Atualizar campos
|
||||
if data.email is not None:
|
||||
# Verificar se email já existe
|
||||
existing = db.query(User).filter(
|
||||
User.email == data.email,
|
||||
User.id != user_id
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="Email já em uso")
|
||||
user.email = data.email
|
||||
|
||||
if data.username is not None:
|
||||
# Verificar se username já existe
|
||||
existing = db.query(User).filter(
|
||||
User.username == data.username,
|
||||
User.id != user_id
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="Username já em uso")
|
||||
user.username = data.username
|
||||
|
||||
if data.full_name is not None:
|
||||
user.full_name = data.full_name
|
||||
|
||||
if data.is_active is not None:
|
||||
user.is_active = data.is_active
|
||||
|
||||
if data.is_verified is not None:
|
||||
user.is_verified = data.is_verified
|
||||
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
return {"message": "Usuário atualizado", "user": user}
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
async def delete_user(
|
||||
user_id: str,
|
||||
admin: User = Depends(get_current_superadmin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Deletar usuário (soft delete - apenas desativa)"""
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Usuário não encontrado")
|
||||
|
||||
if user.is_superadmin:
|
||||
raise HTTPException(status_code=403, detail="Não é possível deletar superadmin")
|
||||
|
||||
user.is_active = False
|
||||
db.commit()
|
||||
|
||||
return {"message": "Usuário desativado com sucesso"}
|
||||
|
||||
@router.post("/users/{user_id}/reset-password")
|
||||
async def reset_user_password(
|
||||
user_id: str,
|
||||
data: PasswordChangeRequest,
|
||||
admin: User = Depends(get_current_superadmin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Resetar senha de usuário"""
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Usuário não encontrado")
|
||||
|
||||
user.password_hash = get_password_hash(data.new_password)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Senha alterada com sucesso"}
|
||||
Reference in New Issue
Block a user