first commit
This commit is contained in:
BIN
app/modules/users/__pycache__/models.cpython-311.pyc
Normal file
BIN
app/modules/users/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/modules/users/__pycache__/repository.cpython-311.pyc
Normal file
BIN
app/modules/users/__pycache__/repository.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/modules/users/__pycache__/route.cpython-311.pyc
Normal file
BIN
app/modules/users/__pycache__/route.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/modules/users/__pycache__/schema.cpython-311.pyc
Normal file
BIN
app/modules/users/__pycache__/schema.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/modules/users/__pycache__/services.cpython-311.pyc
Normal file
BIN
app/modules/users/__pycache__/services.cpython-311.pyc
Normal file
Binary file not shown.
79
app/modules/users/models.py
Normal file
79
app/modules/users/models.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import relationship
|
||||
from database import Base
|
||||
|
||||
import enum
|
||||
|
||||
class Rol(str, enum.Enum):
|
||||
COMPRAS ="compras"
|
||||
VENTAS ="ventas"
|
||||
LOGISTICA ="logistica"
|
||||
ADUANA_SOFT ="aduana_soft"
|
||||
CLIENTE ="cliente"
|
||||
OPERATIVO ="operativo"
|
||||
|
||||
class TipoUsuario(str, enum.Enum):
|
||||
ROOT ="root"
|
||||
ADMIN ="admin"
|
||||
PRIVILEGED ="privileged"
|
||||
ADMIN_LICENCIAS ="admin_licencias"
|
||||
UPDATER = "updater"
|
||||
USER ="user"
|
||||
|
||||
|
||||
class Users(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, nullable=False, index=True, autoincrement=True)
|
||||
#identity
|
||||
name = Column(String(100), nullable=False)
|
||||
middle_Name = Column(String(100), nullable=True)
|
||||
last_Name = Column(String(120), nullable=False)
|
||||
rfc = Column(String(60), nullable=False, unique=True)
|
||||
|
||||
#==== CONEXIONS ====#
|
||||
email = Column(String(120), nullable=False, unique=True)
|
||||
password = Column(String(255), nullable=False)
|
||||
|
||||
#=== credentials
|
||||
rol_operativo = Column(SQLEnum(Rol), default="operativo", nullable=False)
|
||||
tipo_usuario = Column(SQLEnum(TipoUsuario), default="user", nullable=False)
|
||||
permite_diot = Column(Boolean, default=False, nullable=False)
|
||||
|
||||
#==== WHERES
|
||||
enterprise_id = Column(Integer, nullable=True)
|
||||
firma = Column(String(110), nullable=True, unique=True)
|
||||
is_active = Column(Boolean, nullable=False, default=True)
|
||||
|
||||
|
||||
#timestamsp
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
|
||||
deleted_at = Column(DateTime(timezone=True), nullable=True)
|
||||
last_login = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||
|
||||
#trace
|
||||
created_by = Column(Integer, nullable=True)
|
||||
updated_by = Column(Integer, nullable=True)
|
||||
deleted_by = Column(Integer, nullable=True)
|
||||
|
||||
|
||||
#=====
|
||||
# PASSWORD RESET
|
||||
#=====
|
||||
|
||||
password_reset_token = Column(String, nullable=True)
|
||||
password_reset_expires = Column(String, nullable=True)
|
||||
|
||||
|
||||
#relationships
|
||||
clients = relationship("Client", back_populates="user")
|
||||
branch = relationship("Branches", back_populates="user")
|
||||
interactions = relationship("Interacction", back_populates="user")
|
||||
moves = relationship("Moves", back_populates="user", primaryjoin="Users.id == Moves.user_id")
|
||||
licenses = relationship("License", back_populates="user")
|
||||
feed = relationship("Feed", back_populates="user", primaryjoin="Users.id == Feed.user_id")
|
||||
coments = relationship("Coments", back_populates="user", primaryjoin="Users.id == Coments.user_id")
|
||||
affidavit_logs = relationship("AffidavitRecord", back_populates="user", primaryjoin="Users.id == AffidavitRecord.user_id")
|
||||
|
||||
212
app/modules/users/repository.py
Normal file
212
app/modules/users/repository.py
Normal file
@@ -0,0 +1,212 @@
|
||||
# modules/users/repositories.py
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_
|
||||
|
||||
from app.core.baseRepository import BaseRepository
|
||||
from app.modules.users.models import Users, TipoUsuario, Rol
|
||||
from app.core.security import get_password_hash, verify_password
|
||||
|
||||
|
||||
class UserRepository(BaseRepository[Users]):
|
||||
"""Repositorio específico para Users"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
super().__init__(db, Users)
|
||||
|
||||
# ============================================
|
||||
# MÉTODOS DE AUTENTICACIÓN
|
||||
# ============================================
|
||||
|
||||
def get_by_email(self, email: str, include_deleted: bool = False) -> Optional[Users]:
|
||||
"""Obtiene usuario por email"""
|
||||
return self._base_query(include_deleted).filter(Users.email == email).first()
|
||||
|
||||
def get_by_firma(self, firma: str, include_deleted: bool = False) -> Optional[Users]:
|
||||
"""Obtiene usuario por firma digital"""
|
||||
return self._base_query(include_deleted).filter(Users.firma == firma).first()
|
||||
|
||||
def authenticate(self, email: str, password: str) -> Optional[Users]:
|
||||
"""Autentica usuario por email y contraseña"""
|
||||
user = self.get_by_email(email)
|
||||
if not user:
|
||||
return None
|
||||
if not user.is_active or user.deleted_at is not None:
|
||||
return None
|
||||
if not verify_password(password, user.password):
|
||||
return None
|
||||
return user
|
||||
|
||||
def update_last_login(self, user_id: int, ip_address: str) -> Optional[Users]:
|
||||
"""Actualiza timestamp e IP del último login"""
|
||||
user = self.get_by_id(user_id)
|
||||
if user:
|
||||
user.last_login = datetime.utcnow()
|
||||
user.last_ip = ip_address
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
return user
|
||||
|
||||
def change_password(self, user_id: int, new_password: str, updated_by: int = None) -> bool:
|
||||
"""Cambia la contraseña del usuario"""
|
||||
user = self.get_by_id(user_id)
|
||||
if not user:
|
||||
return False
|
||||
|
||||
user.password = get_password_hash(new_password)
|
||||
if updated_by:
|
||||
user.updated_by = updated_by
|
||||
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
# ============================================
|
||||
# MÉTODOS POR TIPO DE USUARIO
|
||||
# ============================================
|
||||
|
||||
def get_by_tipo_usuario(self, tipo_usuario: str, include_deleted: bool = False) -> List[Users]:
|
||||
"""Obtiene usuarios por tipo (root, admin, admin_licencias, user)"""
|
||||
return self._base_query(include_deleted).filter(Users.tipo_usuario == tipo_usuario).all()
|
||||
|
||||
def get_root(self, include_deleted: bool = False) -> List[Users]:
|
||||
"""Usuarios tipo root"""
|
||||
return self.get_by_tipo_usuario(TipoUsuario.ROOT, include_deleted)
|
||||
|
||||
def get_admin(self, include_deleted: bool = False) -> List[Users]:
|
||||
"""Usuarios tipo admin"""
|
||||
return self.get_by_tipo_usuario(TipoUsuario.ADMIN, include_deleted)
|
||||
|
||||
def get_admin_licencias(self, include_deleted: bool = False) -> List[Users]:
|
||||
"""Usuarios tipo admin_licencias"""
|
||||
return self.get_by_tipo_usuario(TipoUsuario.ADMIN_LICENCIAS, include_deleted)
|
||||
|
||||
def get_regular_users(self, include_deleted: bool = False) -> List[Users]:
|
||||
"""Usuarios tipo user (regulares)"""
|
||||
return self.get_by_tipo_usuario(TipoUsuario.USER, include_deleted)
|
||||
|
||||
# ============================================
|
||||
# MÉTODOS POR ROL OPERATIVO
|
||||
# ============================================
|
||||
|
||||
def get_by_rol_operativo(self, rol: str, include_deleted: bool = False) -> List[Users]:
|
||||
"""Obtiene usuarios por rol operativo"""
|
||||
return self._base_query(include_deleted).filter(Users.rol_operativo == rol).all()
|
||||
|
||||
def get_by_permiso_diot(self, include_deleted: bool = False) -> List[Users]:
|
||||
"""Obtiene usuarios que tienen permiso DIOT"""
|
||||
return self._base_query(include_deleted).filter(Users.permite_diot == True).all()
|
||||
|
||||
# ============================================
|
||||
# MÉTODOS POR ASOCIACIONES
|
||||
# ============================================
|
||||
|
||||
def get_by_enterprise(self, enterprise_id: int, include_deleted: bool = False) -> List[Users]:
|
||||
"""Obtiene usuarios de una empresa específica"""
|
||||
return self._base_query(include_deleted).filter(Users.enterprise_id == enterprise_id).all()
|
||||
|
||||
def get_by_sucursal(self, sucursal_id: int, include_deleted: bool = False) -> List[Users]:
|
||||
"""Obtiene usuarios de una sucursal específica"""
|
||||
return self._base_query(include_deleted).filter(Users.sucursales_id == sucursal_id).all()
|
||||
|
||||
def get_by_licencia(self, licencia_id: int, include_deleted: bool = False) -> List[Users]:
|
||||
"""Obtiene usuarios asociados a una licencia"""
|
||||
return self._base_query(include_deleted).filter(Users.licencia_id == licencia_id).all()
|
||||
|
||||
def get_by_perfil(self, perfil_id: int, include_deleted: bool = False) -> List[Users]:
|
||||
"""Obtiene usuarios por perfil"""
|
||||
return self._base_query(include_deleted).filter(Users.perfil_id == perfil_id).all()
|
||||
|
||||
# ============================================
|
||||
# MÉTODOS DE ESTADO
|
||||
# ============================================
|
||||
|
||||
def get_active(self, skip: int = 0, limit: int = 100) -> List[Users]:
|
||||
"""Obtiene usuarios activos (no eliminados)"""
|
||||
return self.get_all(skip=skip, limit=limit, include_deleted=False)
|
||||
|
||||
def get_inactive(self, skip: int = 0, limit: int = 100) -> List[Users]:
|
||||
"""Obtiene usuarios inactivos o eliminados"""
|
||||
return self._base_query(include_deleted=True).filter(
|
||||
or_(
|
||||
Users.is_active == False,
|
||||
Users.deleted_at.isnot(None)
|
||||
)
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
def activate(self, user_id: int, updated_by: int = None) -> Optional[Users]:
|
||||
"""Activa un usuario inactivo"""
|
||||
user = self.get_by_id(user_id, include_deleted=True)
|
||||
if not user:
|
||||
return None
|
||||
|
||||
user.is_active = True
|
||||
if user.deleted_at:
|
||||
user.deleted_at = None
|
||||
if updated_by:
|
||||
user.updated_by = updated_by
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
return user
|
||||
|
||||
def deactivate(self, user_id: int, updated_by: int = None) -> Optional[Users]:
|
||||
"""Desactiva un usuario (sin soft delete)"""
|
||||
user = self.get_by_id(user_id)
|
||||
if not user:
|
||||
return None
|
||||
|
||||
user.is_active = False
|
||||
if updated_by:
|
||||
user.updated_by = updated_by
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
return user
|
||||
|
||||
# ============================================
|
||||
# MÉTODOS DE BÚSQUEDA
|
||||
# ============================================
|
||||
|
||||
def search(self, term: str, skip: int = 0, limit: int = 100) -> List[Users]:
|
||||
"""Búsqueda por nombre, email o RFC"""
|
||||
search_term = f"%{term}%"
|
||||
return self._base_query().filter(
|
||||
or_(
|
||||
Users.name.ilike(search_term),
|
||||
Users.last_name.ilike(search_term),
|
||||
Users.email.ilike(search_term),
|
||||
Users.rfc.ilike(search_term)
|
||||
)
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
def count_by_enterprise(self, enterprise_id: int) -> int:
|
||||
"""Cuenta usuarios por empresa"""
|
||||
return self._base_query().filter(Users.enterprise_id == enterprise_id).count()
|
||||
|
||||
def count_by_tipo(self, tipo_usuario: str) -> int:
|
||||
"""Cuenta usuarios por tipo"""
|
||||
return self._base_query().filter(Users.tipo_usuario == tipo_usuario).count()
|
||||
|
||||
# ============================================
|
||||
# SOBRESCRITURA DE MÉTODOS BASE CON TRAZABILIDAD
|
||||
# ============================================
|
||||
|
||||
def create(self, data: Dict[str, Any], created_by: int = None) -> Users:
|
||||
"""Crea usuario con hash de contraseña"""
|
||||
create_data = data.copy()
|
||||
|
||||
# Hashear contraseña si existe
|
||||
if "password" in create_data:
|
||||
raw_password = create_data["password"]
|
||||
print(f"HASHEANDO PASSWORD: {raw_password} (longitud: {len(raw_password)})")
|
||||
create_data["password"] = get_password_hash(raw_password)
|
||||
print(f"HASH GENERADO: {create_data['password'][:20]}...")
|
||||
|
||||
return super().create(create_data, created_by)
|
||||
|
||||
def update(self, id: int, data: Dict[str, Any], updated_by: int = None, include_deleted: bool = False) -> Optional[Users]:
|
||||
"""Actualiza usuario, hashea contraseña si viene"""
|
||||
if "password" in data:
|
||||
data["password"] = get_password_hash(data["password"])
|
||||
return super().update(id, data, updated_by, include_deleted)
|
||||
188
app/modules/users/route.py
Normal file
188
app/modules/users/route.py
Normal file
@@ -0,0 +1,188 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Header, status
|
||||
#
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional, List
|
||||
#
|
||||
from database import get_db
|
||||
from app.core.auth import get_current_user
|
||||
from app.modules.users.models import Users
|
||||
#
|
||||
from app.modules.users.schema import UserCreate, UserResponse, UserLogin, LoginResponse, DeleteResponse, UpdateUser
|
||||
from app.modules.users.services import UserCrudService
|
||||
from app.core.security import create_access_token, verify_password
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
security = HTTPBearer()
|
||||
|
||||
#================ Login ====================
|
||||
@router.post("/login", response_model=LoginResponse)
|
||||
async def login(
|
||||
login_data: UserLogin,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Authenticate user and return JWT token"""
|
||||
|
||||
user = db.query(Users).filter(Users.email == login_data.email).first()
|
||||
|
||||
#check email
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code = status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Credentials invalid - user not found",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
#check password ...
|
||||
if not verify_password(login_data.password, user.password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid credentials = worng password",
|
||||
headers={"WWW-Athenticate": "Bearer"}
|
||||
)
|
||||
|
||||
#check is active
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="This user is ianctive, contact with support"
|
||||
)
|
||||
|
||||
#update last login
|
||||
from datetime import datetime
|
||||
user.last_login = datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
#create token
|
||||
token_data = {
|
||||
"sub": str(user.id),
|
||||
"email": user.email,
|
||||
"user_type": user.tipo_usuario,
|
||||
"rol_operativo": user.rol_operativo
|
||||
}
|
||||
access_token = create_access_token(token_data)
|
||||
|
||||
return LoginResponse(access_token=access_token, token_type="bearer")
|
||||
|
||||
|
||||
|
||||
#================ Register ====================
|
||||
|
||||
@router.post("/register", response_model=UserResponse)
|
||||
async def register(data: UserCreate, db: Session = Depends(get_db)):
|
||||
|
||||
user_count = db.query(Users).count()
|
||||
|
||||
if user_count == 0:
|
||||
service = UserCrudService()
|
||||
return UserCrudService.create_user(db = db, user_create=data, current_user=None)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Root user already exists, use create_users enpoint"
|
||||
)
|
||||
|
||||
#================ Create User ====================
|
||||
@router.post("/create_users", response_model=UserResponse)
|
||||
async def new_user(
|
||||
data: UserCreate,
|
||||
db: Session = Depends(get_db),
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security)
|
||||
):
|
||||
|
||||
"""Create a new user - Requeres authentication """
|
||||
|
||||
from app.core.auth import AuthService
|
||||
|
||||
token = credentials.credentials
|
||||
auth_service = AuthService()
|
||||
payload = auth_service.decode_token(token)
|
||||
user_id = int(payload.get("sub"))
|
||||
current_user = db.query(Users).filter(Users.id == user_id).first()
|
||||
|
||||
|
||||
if not current_user:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="User not found - invalid token"
|
||||
)
|
||||
|
||||
if current_user.tipo_usuario not in ["root", "admin"]:
|
||||
raise HTTPException(status_code=403, detail="you dont have permission to create users")
|
||||
|
||||
if current_user.tipo_usuario == "admin" and data.tipo_usuario != "user":
|
||||
raise HTTPException(status_code=403, detail="Admin can only create users type user")
|
||||
|
||||
return UserCrudService.create_user(
|
||||
db = db, user_create = data, current_user = current_user
|
||||
)
|
||||
|
||||
#================ Get Users ====================
|
||||
@router.get("/get", response_model=List[UserResponse])
|
||||
def obtain_users(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Users = Depends(get_current_user),
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip for pagination"),
|
||||
limit: int = Query(100, ge=1, le= 1000, description="Maximun number of records to return")
|
||||
):
|
||||
"""Get a list of user with pagination - requeres authentication"""
|
||||
return UserCrudService.get_users(db = db, skip=skip, limit=limit)
|
||||
|
||||
#================ Delete Users ====================
|
||||
@router.delete("/delete/{user_id}", response_model=DeleteResponse)
|
||||
async def delete(
|
||||
db:Session = Depends(get_db),
|
||||
user_id= int,
|
||||
current_user: Users = Depends(get_current_user)
|
||||
):
|
||||
"""Delete a user by id"""
|
||||
|
||||
current_user = db.query(Users).filter(Users.id == user_id).first()
|
||||
|
||||
if current_user.tipo_usuario not in ["root", "admin"]:
|
||||
raise HTTPException(status_code=403,
|
||||
detail="You dont have permitions to delete ")
|
||||
try:
|
||||
UserCrudService.delete_user(db = db, user_id=user_id, current_user=current_user)
|
||||
return {"message": "ok"}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
#================ Update Users ====================
|
||||
|
||||
@router.patch("/update/{user_id}", response_model=UserResponse)
|
||||
async def user_update(
|
||||
user_id: int,
|
||||
data: UpdateUser,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Users = Depends(get_current_user)
|
||||
):
|
||||
""" Update existing user"""
|
||||
|
||||
user = db.query(Users).filter(Users.id == user_id).first()
|
||||
|
||||
current_user = db.query(Users).filter(Users.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
raise ValueError("Usuario no encontrado")
|
||||
|
||||
if current_user.id != user_id:
|
||||
if current_user.tipo_usuario not in ["root", "admin"]:
|
||||
raise PermissionError("Admin only can update user ur enterprise")
|
||||
try:
|
||||
updated_user = await UserCrudService.Update_user(db=db, user_id=user_id, user_data=data, current_user=current_user)
|
||||
print(f"TIPO DE updated_user: {type(updated_user)}")
|
||||
print(f"VALOR: {updated_user}")
|
||||
print(f"ID: {updated_user.id if updated_user else 'None'}")
|
||||
return UserResponse.model_validate(updated_user)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
|
||||
except PermissionError as e:
|
||||
raise HTTPException(status_code=403, detail=str(e))
|
||||
|
||||
|
||||
|
||||
|
||||
96
app/modules/users/schema.py
Normal file
96
app/modules/users/schema.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# modules/users/schemas.py
|
||||
from pydantic import BaseModel, EmailStr, Field, validator
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
#
|
||||
import re
|
||||
from enum import Enum
|
||||
|
||||
class TipoUsuario(str, Enum):
|
||||
ROOT = "root"
|
||||
ADMIN = "admin"
|
||||
ADMIN_LICENCIAS = "admin_licencias"
|
||||
USER = "user"
|
||||
|
||||
class RolOperativo(str, Enum):
|
||||
COMPRAS ="compras"
|
||||
VENTAS ="ventas"
|
||||
LOGISTICA ="logistica"
|
||||
ADUANA_SOFT ="aduana_soft"
|
||||
CLIENTE ="cliente"
|
||||
OPERATIVO ="operativo"
|
||||
|
||||
# ============================================
|
||||
# BASE SCHEMAS
|
||||
# ============================================
|
||||
class UserBase(BaseModel):
|
||||
name: str = Field(...)
|
||||
middle_Name: Optional[str] = Field(None)
|
||||
last_Name: str = Field(...)
|
||||
rfc: str = Field(..., example="PORF8513JH3")
|
||||
email: EmailStr
|
||||
tipo_usuario: TipoUsuario
|
||||
rol_operativo: RolOperativo
|
||||
permite_diot: bool = False
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str = Field(..., min_length=8)
|
||||
enterprise_id: Optional[int] = Field(None)
|
||||
firma: Optional[str] = Field(None)
|
||||
|
||||
|
||||
@validator("password")
|
||||
def validate_password(cls, v):
|
||||
if len(v) < 8:
|
||||
raise ValueError("La contrasena debe tener al menos 8 caracteres")
|
||||
if not re.search(r"[A-Z]", v):
|
||||
raise ValueError("La contrasena debe contenera la emnos una mayuscula")
|
||||
return v
|
||||
|
||||
|
||||
class UserResponse(UserBase):
|
||||
id: int
|
||||
is_active: bool
|
||||
last_login: datetime
|
||||
created_by: Optional[int]
|
||||
updated_by: Optional[int]
|
||||
deleted_by: Optional[int]
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime]
|
||||
deleted_at: Optional[datetime]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UpdateUser(UserBase):
|
||||
name: Optional[str] = None
|
||||
middle_Name: Optional[str] = None
|
||||
last_Name: Optional[str] = None
|
||||
rfc: Optional[str] = None
|
||||
email: Optional[EmailStr] = None
|
||||
permite_diot: bool = None
|
||||
rol_operativo: Optional[RolOperativo] = None
|
||||
tipo_usuario: Optional[TipoUsuario] = None
|
||||
firma: Optional[str] = Field(None, max_length=110)
|
||||
brnaches_id: Optional[int] = None
|
||||
license_id: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
#============================================
|
||||
class UserLogin(BaseModel):
|
||||
email: EmailStr = Field(..., examples=["User@example.com"])
|
||||
password: str = Field(..., min_length=8)
|
||||
|
||||
|
||||
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
class DeleteResponse(BaseModel):
|
||||
message: str
|
||||
158
app/modules/users/services.py
Normal file
158
app/modules/users/services.py
Normal file
@@ -0,0 +1,158 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
#
|
||||
from sqlalchemy.orm import Session
|
||||
import logging
|
||||
from app.modules.users.models import Users, TipoUsuario, Rol
|
||||
#
|
||||
from app.modules.users.schema import UserCreate, UserResponse, UpdateUser
|
||||
from app.modules.users.repository import UserRepository
|
||||
from app.core.security import get_password_hash
|
||||
#
|
||||
from app.core.auth import get_current_user
|
||||
from app.core.baseRepository import BaseRepository
|
||||
from app.pipelines.crud import CreatePipe
|
||||
|
||||
class UserCrudService:
|
||||
|
||||
@staticmethod
|
||||
def create_user(db:Session, user_create: UserCreate, current_user: Optional[Users] = None) -> UserResponse:
|
||||
|
||||
""" Create a new user in the database and approved if is root user"""
|
||||
first = db.query(Users).count()
|
||||
is_first_user = (first == 0)
|
||||
|
||||
user_data = user_create.dict()
|
||||
#
|
||||
#
|
||||
model_data = {
|
||||
"name": user_data.get("name"),
|
||||
"middle_Name": user_data.get("middle_Name"),
|
||||
"last_Name": user_data.get("last_Name"),
|
||||
"rfc": user_data.get("rfc"),
|
||||
"email": user_data.get("email"),
|
||||
"rol_operativo": user_data.get("rol_operativo"),
|
||||
"tipo_usuario": user_data.get("tipo_usuario"),
|
||||
"permite_diot": user_data.get("permite_diot", False),
|
||||
"enterprise_id": user_data.get("enterprise_id"),
|
||||
"firma": user_data.get("firma"),
|
||||
"is_active": True,
|
||||
}
|
||||
|
||||
#Password comes raw since schema
|
||||
plain_pass = user_data.get("password")
|
||||
|
||||
|
||||
# this step is for hash the pasword
|
||||
hashed_pass = get_password_hash(plain_pass)
|
||||
model_data["password"] = hashed_pass
|
||||
|
||||
# first user or not that, this is a joke :)
|
||||
|
||||
|
||||
if is_first_user:
|
||||
# if first user is being created, it must be root and approved by default
|
||||
model_data["tipo_usuario"] = TipoUsuario.ROOT.value
|
||||
model_data["created_by"] = None
|
||||
|
||||
#this point is for make one instance
|
||||
new_user = Users(**model_data)
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
|
||||
else:
|
||||
# Case B: aditional User
|
||||
if not current_user:
|
||||
raise ValueError("requeries authentication for create user")
|
||||
|
||||
#validate who is creating or if intent create a root user
|
||||
if model_data["tipo_usuario"] == TipoUsuario.ROOT.value:
|
||||
if current_user.tipo_usuario != TipoUsuario.ROOT.value:
|
||||
raise ValueError(" only ROOT user can create another Root user")
|
||||
|
||||
model_data["created_by"] = current_user.id
|
||||
new_user = Users(**model_data)
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
|
||||
#to this point validate the next users if can be used the app, admin, license_admin and users
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return UserResponse.model_validate(new_user)
|
||||
# if first is registered allthem users only can set admin or user role.
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_users(db: Session, skip: int =0, limit: int = 100) -> List[UserResponse]:
|
||||
""""Get a list of users whith pagination"""
|
||||
userRes = db.query(Users).offset(skip).limit(limit).all()
|
||||
|
||||
return [UserResponse.model_validate(user) for user in userRes]
|
||||
|
||||
@staticmethod
|
||||
def delete_user(db: Session, user_id: int, current_user: Users) -> None:
|
||||
""" this is soft delete, for traceability"""
|
||||
user = db.query(Users).filter(Users.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
raise ValueError(
|
||||
"User not found")
|
||||
if user.tipo_usuario == TipoUsuario.ROOT.value:
|
||||
raise ValueError("Root user cannot be deleted")
|
||||
user.is_active = False
|
||||
user.deleted_by = current_user.id
|
||||
user.deleted_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"message": f"User has been deleted"
|
||||
}
|
||||
|
||||
#=========== UPDATE =====================
|
||||
@staticmethod
|
||||
async def Update_user(db: Session, user_id: int, user_data: UpdateUser, current_user: Users) -> Users:
|
||||
"""Function to updates users, only personal data"""
|
||||
user = db.query(Users).filter(Users.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
raise ValueError(
|
||||
"User not found")
|
||||
|
||||
if current_user.id != user_id:
|
||||
|
||||
if current_user.tipo_usuario not in ["root", "admin"]:
|
||||
raise PermissionError("You don't have permition to updated")
|
||||
|
||||
if current_user.tipo_usuario == "admin":
|
||||
if user.enterprise_id != current_user.enterprise_id:
|
||||
raise PermissionError(" Admin only can't update user enterprise ")
|
||||
|
||||
update_data = user_data.dict(exclude_unset=True)
|
||||
|
||||
if user.tipo_usuario == "root" and update_data["tipo_usuario"] != "root":
|
||||
root_count = db.query(Users).filter(
|
||||
Users.tipo_usuario == "root",
|
||||
Users.deleted_at.is_(None)
|
||||
).count
|
||||
if root_count <= 1:
|
||||
raise ValueError("Can't downgrade ROOT user")
|
||||
|
||||
for field, value in update_data.items():
|
||||
if hasattr(user, field):
|
||||
setattr(user, field, value)
|
||||
|
||||
user.updated_by = current_user.id
|
||||
user.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user