First commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Exportar los módulos principales
|
||||
from .database import Base, get_db
|
||||
from . import models
|
||||
from . import schemas
|
||||
|
||||
__all__ = ["Base", "get_db", "models", "schemas"]
|
||||
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
|
||||
from database import SessionLocal
|
||||
from models import User
|
||||
from services import AuthService
|
||||
from os import getenv
|
||||
|
||||
def create_admin():
|
||||
email = getenv("ADMIN_EMAIL")
|
||||
password = getenv("ADMIN_PASSWORD")
|
||||
|
||||
if not email or not password:
|
||||
raise RuntimeError("ADMIN_EMAIL y ADMIN_PASSWORD is required.")
|
||||
if len(password) < 8:
|
||||
raise RuntimeError("ADMIN_PASSWORD need almost 8 caracters.")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
existing = db.query(User).filter(User.email == email).first()
|
||||
if existing:
|
||||
print(f"[INFO] Admin '{email}' already exists, skipping creation.")
|
||||
return
|
||||
admin = User(
|
||||
name='Admin',
|
||||
email=email,
|
||||
password=AuthService.hash_password(password),
|
||||
rol='admin',
|
||||
active=True
|
||||
)
|
||||
db.add(admin)
|
||||
db.commit()
|
||||
print(f"[INFO] Admin '{email}' created successfully.")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"[ERROR] Failed to create admin: {e}", file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,231 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
import re
|
||||
|
||||
|
||||
from database import get_db
|
||||
from models import User
|
||||
from schemas import (
|
||||
UserCreate, UserUpdate, UserResponse, UserWithModifications,
|
||||
RoleChangeRequest, LoginRequest, Token, ModificationResponse
|
||||
)
|
||||
from services import UserService, AuthService
|
||||
from dependencies import get_current_user, require_admin, require_admin_or_owner, oauth2_scheme
|
||||
|
||||
router = APIRouter()
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
||||
# Rutas de Autenticación
|
||||
@router.post("/login", response_model=Token)
|
||||
def login(
|
||||
login_data: LoginRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
user = AuthService.authenticate_user(db, login_data.email, login_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
)
|
||||
|
||||
access_token = AuthService.create_access_token(data={"user_id": user.id})
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"user": user
|
||||
}
|
||||
|
||||
# Rutas de Usuarios
|
||||
@router.get("/users/", response_model=List[UserResponse])
|
||||
def get_all_users(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
return user_service.get_all_users(skip, limit)
|
||||
|
||||
@router.get("/users/pending", response_model=List[UserResponse])
|
||||
def get_pending_users(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
return user_service.get_pending_users(skip, limit)
|
||||
|
||||
@router.get("/users/me", response_model=UserResponse)
|
||||
def get_current_user_info(current_user: User = Depends(get_current_user)):
|
||||
return current_user
|
||||
|
||||
@router.get("/users/{user_id}", response_model=UserWithModifications)
|
||||
def get_user(
|
||||
user_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
user = user_service.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# Verificar permisos (admin o el propio usuario)
|
||||
if current_user.rol != 'admin' and current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
return user
|
||||
|
||||
@router.post("/users/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_user(
|
||||
user_data: UserCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
ip_address = request.client.host if request.client else None
|
||||
return user_service.create_user(user_data, current_user.id, ip_address)
|
||||
|
||||
@router.post("/users/common", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||
@limiter.limit("5/minute")
|
||||
def create_common_user(
|
||||
user_data: UserCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Endpoint público para que cualquier usuario (no autenticado) pueda registrarse.
|
||||
|
||||
The created user will always have `rol='operator'` and `active=False`.
|
||||
For audit purposes, when no authenticated updater is available we record the
|
||||
created user itself as the `updater` (self-created).
|
||||
"""
|
||||
user_service = UserService(db)
|
||||
ip_address = request.client.host if request.client else None
|
||||
|
||||
# Ensure role and active defaults regardless of input
|
||||
user_data = user_data.copy(update={"rol": "operator", "active": False})
|
||||
|
||||
# Pass updater_id=None so the service will mark the new user as the updater
|
||||
return user_service.create_user(user_data, updater_id=None, ip_address=ip_address)
|
||||
|
||||
@router.get("/logs/users", response_model=List[ModificationResponse])
|
||||
def get_users_modifications(
|
||||
skip: int = 0,
|
||||
limit: int = 200,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
# Verificar permisos
|
||||
if current_user.rol != 'admin':
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
user_service = UserService(db)
|
||||
return user_service.get_users_modifications(skip, limit)
|
||||
|
||||
@router.put("/users/{user_id}", response_model=UserResponse)
|
||||
def update_user(
|
||||
user_id: int,
|
||||
user_data: UserUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
|
||||
# Verificar permisos
|
||||
if current_user.rol != 'admin' and current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
edited_user = user_service.get_user_by_id(user_id)
|
||||
|
||||
"""Verify is the desactivated used is and admin, in this case check if there is at least another active admin before allowing the activation."""
|
||||
if user_data.active is not None and edited_user.rol == 'admin' and user_data.active == False:
|
||||
active_admins = user_service.count_active_admins()
|
||||
if active_admins <= 1:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Cannot deactivate this user because it is the only active admin. Please activate another admin first."
|
||||
)
|
||||
|
||||
|
||||
ip_address = request.client.host if request.client else None
|
||||
return user_service.update_user(user_id, user_data, current_user.id, ip_address)
|
||||
|
||||
@router.patch("/users/{user_id}/role", response_model=UserResponse)
|
||||
def change_user_role(
|
||||
user_id: int,
|
||||
role_data: RoleChangeRequest,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
ip_address = request.client.host if request.client else None
|
||||
return user_service.change_user_role(user_id, role_data.new_role, current_user.id, ip_address)
|
||||
|
||||
@router.post("/users/{user_id}/deactivate", response_model=UserResponse)
|
||||
def deactivate_user(
|
||||
user_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
ip_address = request.client.host if request.client else None
|
||||
"""Verify is the desactivated used is and admin, in this case check if there is at least another active admin before allowing the activation."""
|
||||
if current_user.rol == 'admin':
|
||||
desactivated_user = user_service.get_user_by_id(user_id)
|
||||
if desactivated_user and desactivated_user.rol == 'admin':
|
||||
active_admins = user_service.count_active_admins()
|
||||
if active_admins <= 1:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Cannot activate this user because it is the only active admin. Please activate another admin first."
|
||||
)
|
||||
return user_service.deactivate_user(user_id, current_user.id, ip_address)
|
||||
|
||||
@router.post("/users/{user_id}/activate", response_model=UserResponse)
|
||||
def activate_user(
|
||||
user_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
ip_address = request.client.host if request.client else None
|
||||
return user_service.activate_user(user_id, current_user.id, ip_address)
|
||||
|
||||
@router.get("/users/{user_id}/modifications", response_model=List[ModificationResponse])
|
||||
def get_user_modifications(
|
||||
user_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 200,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
# Verificar permisos
|
||||
if current_user.rol != 'admin' and current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
user_service = UserService(db)
|
||||
return user_service.get_user_modifications(user_id, skip, limit)
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}", response_model=UserResponse)
|
||||
def delete_user(
|
||||
user_id: int,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
user_service = UserService(db)
|
||||
ip_address = request.client.host if request.client else None
|
||||
if current_user.rol != 'admin' and current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
return user_service.delete_user(user_id, current_user.id, ip_address)
|
||||
@@ -0,0 +1,24 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import os
|
||||
|
||||
# Configuración de la base de datos
|
||||
SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./users.db")
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL,
|
||||
connect_args={"check_same_thread": False} if SQLALCHEMY_DATABASE_URL.startswith("sqlite") else {}
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
# Dependencia para obtener la sesión de la base de datos
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,49 @@
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database import get_db
|
||||
from services import AuthService, UserService
|
||||
from models import User
|
||||
|
||||
# Configuración OAuth2
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login")
|
||||
|
||||
# Dependencias de seguridad
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db),
|
||||
token: str = Depends(oauth2_scheme)
|
||||
) -> User:
|
||||
user_id = AuthService.verify_token(token)
|
||||
user_service = UserService(db)
|
||||
user = user_service.get_user_by_id(user_id)
|
||||
if not user or not user.active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token or inactive user",
|
||||
)
|
||||
return user
|
||||
|
||||
def get_current_active_user(current_user: User = Depends(get_current_user)):
|
||||
if not current_user.active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
|
||||
def require_admin(current_user: User = Depends(get_current_active_user)):
|
||||
if current_user.rol != 'admin':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Insufficient permissions"
|
||||
)
|
||||
return current_user
|
||||
|
||||
def require_admin_or_owner(
|
||||
user_id: int,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
if current_user.rol != 'admin' and current_user.id != user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Insufficient permissions"
|
||||
)
|
||||
return current_user
|
||||
@@ -0,0 +1,44 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import crud
|
||||
from database import engine
|
||||
from models import Base
|
||||
from create_admin import create_admin
|
||||
from os import getenv
|
||||
|
||||
# Crear tablas
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(title="User Management API", version="1.0.0")
|
||||
|
||||
# Configurar CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[getenv("FRONT_END_URL")],
|
||||
allow_credentials=True,
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
)
|
||||
|
||||
# Incluir rutas
|
||||
app.include_router(crud.router, prefix="/api/v1", tags=["api"])
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "User Management API"}
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
return {"status": "healthy"}
|
||||
|
||||
##Eliminar en producción
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Ejecutar scraper síncrono en thread separado"""
|
||||
# Usar asyncio.to_thread para ejecutar código síncrono
|
||||
create_admin()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
@@ -0,0 +1,40 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Text, ForeignKey, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from database import Base
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
email = Column(String(100), unique=True, nullable=False)
|
||||
password = Column(String(255), nullable=False)
|
||||
rol = Column(String(50), default='operator')
|
||||
active = Column(Boolean, default=True)
|
||||
creation_time = Column(DateTime, default=datetime.utcnow)
|
||||
update_time = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
modifications = relationship('UserAudit', back_populates='user',
|
||||
foreign_keys='UserAudit.user_id')
|
||||
modifications_made = relationship('UserAudit', back_populates='updater',
|
||||
foreign_keys='UserAudit.updater_id')
|
||||
|
||||
class UserAudit(Base):
|
||||
__tablename__ = 'users_audit'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
||||
updater_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
||||
|
||||
action = Column(String(50), nullable=False)
|
||||
updated_attribute = Column(String(100))
|
||||
before_value = Column(Text)
|
||||
after_value = Column(Text)
|
||||
snapshot = Column(JSON)
|
||||
|
||||
modification_date = Column(DateTime, default=datetime.utcnow)
|
||||
ip_address = Column(String(45))
|
||||
|
||||
user = relationship('User', foreign_keys=[user_id], back_populates='modifications')
|
||||
updater = relationship('User', foreign_keys=[updater_id], back_populates='modifications_made')
|
||||
@@ -0,0 +1,67 @@
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
# User Schemas
|
||||
class UserBase(BaseModel):
|
||||
name: str
|
||||
email: EmailStr
|
||||
rol: str = "operator"
|
||||
active: bool = True
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
email: Optional[EmailStr] = None
|
||||
password: Optional[str] = None
|
||||
rol: Optional[str] = None
|
||||
active: Optional[bool] = None
|
||||
|
||||
class UserResponse(UserBase):
|
||||
id: int
|
||||
creation_time: datetime
|
||||
update_time: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class UserWithModifications(UserResponse):
|
||||
modifications: List["ModificationSimple"] = []
|
||||
|
||||
# Audit Schemas
|
||||
class ModificationSimple(BaseModel):
|
||||
id: int
|
||||
action: str
|
||||
updated_attribute: Optional[str] = None
|
||||
before_value: Optional[str] = None
|
||||
after_value: Optional[str] = None
|
||||
modification_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class ModificationResponse(ModificationSimple):
|
||||
user: Optional[UserResponse] = None
|
||||
updater: Optional[UserResponse] = None
|
||||
|
||||
# Auth Schemas
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
user: UserResponse
|
||||
|
||||
class TokenData(BaseModel):
|
||||
user_id: Optional[int] = None
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
class RoleChangeRequest(BaseModel):
|
||||
new_role: str
|
||||
|
||||
# Resolver referencias circulares
|
||||
UserWithModifications.model_rebuild()
|
||||
ModificationResponse.model_rebuild()
|
||||
@@ -0,0 +1,333 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import HTTPException, status
|
||||
from datetime import datetime, timedelta
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from models import User, UserAudit
|
||||
from schemas import UserCreate, UserUpdate
|
||||
|
||||
# Configuración de seguridad
|
||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||
if not SECRET_KEY:
|
||||
raise RuntimeError("SECRET_KEY no configurada. Abortando inicio.")
|
||||
if len(SECRET_KEY) < 20:
|
||||
raise RuntimeError("SECRET_KEY demasiado corta")
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
class AuthService:
|
||||
@staticmethod
|
||||
def hash_password(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
@staticmethod
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
@staticmethod
|
||||
def authenticate_user(db: Session, email: str, password: str) -> Optional[User]:
|
||||
user = db.query(User).filter(User.email == email, User.active == True).first()
|
||||
if not user:
|
||||
return None
|
||||
if not AuthService.verify_password(password, user.password):
|
||||
return None
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
@staticmethod
|
||||
def verify_token(token: str):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
user_id: int = payload.get("user_id")
|
||||
if user_id is None:
|
||||
raise credentials_exception
|
||||
return user_id
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
class UserService:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def get_user_by_id(self, user_id: int) -> Optional[User]:
|
||||
return self.db.query(User).filter(User.id == user_id).first()
|
||||
|
||||
def get_user_by_email(self, email: str) -> Optional[User]:
|
||||
return self.db.query(User).filter(User.email == email).first()
|
||||
|
||||
def get_all_users(self, skip: int = 0, limit: int = 100):
|
||||
return self.db.query(User).offset(skip).limit(limit).all()
|
||||
|
||||
def get_pending_users(self, skip: int = 0, limit: int = 100):
|
||||
return self.db.query(User).filter(User.active == False).offset(skip).limit(limit).all()
|
||||
|
||||
def count_active_admins(self) -> int:
|
||||
"""Helper function to count the number of active admin users."""
|
||||
return self.db.query(User).filter(User.rol == 'admin', User.active == True).count()
|
||||
|
||||
def create_user(self, user_data: UserCreate, updater_id: Optional[int] = None, ip_address: str = None) -> User:
|
||||
if self.get_user_by_email(user_data.email):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Failed to create user"
|
||||
)
|
||||
|
||||
if user_data.rol not in ['admin', 'operator']:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid role specified"
|
||||
)
|
||||
|
||||
if user_data.password and len(user_data.password) < 8:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password must be at least 8 characters long"
|
||||
)
|
||||
|
||||
hashed_password = AuthService.hash_password(user_data.password)
|
||||
|
||||
user = User(
|
||||
name=user_data.name,
|
||||
email=user_data.email,
|
||||
password=hashed_password,
|
||||
rol=user_data.rol,
|
||||
active=user_data.active
|
||||
)
|
||||
|
||||
self.db.add(user)
|
||||
self.db.flush()
|
||||
|
||||
# If no updater_id provided (unauthenticated creation), mark the user as the updater (self-created)
|
||||
audit_updater = updater_id if updater_id is not None else user.id
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user.id,
|
||||
updater_id=audit_updater,
|
||||
action="create",
|
||||
after_value=f"User created with role: {user.rol}",
|
||||
snapshot={
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
},
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def update_user(self, user_id: int, user_data: UserUpdate, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
if user_data.rol not in ['admin', 'operator']:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid role specified"
|
||||
)
|
||||
|
||||
if user_data.password and len(user_data.password) < 8:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password must be at least 8 characters long"
|
||||
)
|
||||
|
||||
changes = []
|
||||
snapshot = {
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
}
|
||||
|
||||
for field, value in user_data.dict(exclude_unset=True).items():
|
||||
if hasattr(user, field):
|
||||
old_value = getattr(user, field)
|
||||
|
||||
if field == "password":
|
||||
value = AuthService.hash_password(value)
|
||||
old_value = "***"
|
||||
|
||||
if old_value != value:
|
||||
setattr(user, field, value)
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="update",
|
||||
updated_attribute=field,
|
||||
before_value=str(old_value),
|
||||
after_value=str(value) if field != "password" else "***",
|
||||
snapshot=snapshot,
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
changes.append(field)
|
||||
|
||||
if changes:
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def change_user_role(self, user_id: int, new_role: str, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
old_role = user.rol
|
||||
snapshot = {
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
}
|
||||
|
||||
user.rol = new_role
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="role_change",
|
||||
updated_attribute="rol",
|
||||
before_value=old_role,
|
||||
after_value=new_role,
|
||||
snapshot=snapshot,
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def deactivate_user(self, user_id: int, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
user.active = False
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="deactivate",
|
||||
updated_attribute="active",
|
||||
before_value="True",
|
||||
after_value="False",
|
||||
snapshot={
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": True
|
||||
},
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def activate_user(self, user_id: int, updater_id: int, ip_address: str = None) -> User:
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
user.active = True
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="activate",
|
||||
updated_attribute="active",
|
||||
before_value="False",
|
||||
after_value="True",
|
||||
snapshot={
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": False
|
||||
},
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
self.db.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
def get_user_modifications(self, user_id: int, skip: int = 0, limit: int = 50):
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
return (self.db.query(UserAudit)
|
||||
.filter(UserAudit.user_id == user_id)
|
||||
.order_by(UserAudit.modification_date.desc())
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all())
|
||||
|
||||
def get_users_modifications(self, skip: int = 0, limit: int = 100):
|
||||
return (self.db.query(UserAudit)
|
||||
.order_by(UserAudit.modification_date.desc())
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all())
|
||||
|
||||
def delete_user(self, user_id: int, updater_id: int, ip_address: str = None):
|
||||
user = self.get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
snapshot = {
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"rol": user.rol,
|
||||
"active": user.active
|
||||
}
|
||||
|
||||
self.db.delete(user)
|
||||
|
||||
audit = UserAudit(
|
||||
user_id=user_id,
|
||||
updater_id=updater_id,
|
||||
action="delete",
|
||||
after_value="User deleted",
|
||||
snapshot=snapshot,
|
||||
ip_address=ip_address
|
||||
)
|
||||
self.db.add(audit)
|
||||
self.db.commit()
|
||||
Reference in New Issue
Block a user