Skip to main content

Overview

User management in the Inventory Management System is exclusively available to administrators. This includes creating new users, assigning roles, updating user information, and deactivating accounts.
All user management operations require the admin role. Gestor and consultor roles cannot access these endpoints.

Architecture

User management is implemented using clean architecture:
  • Controller: backend/User/Adapters/user_controller.py
  • Service Layer: backend/User/Domain/user_service.py
  • Domain Model: backend/User/Domain/user.py
  • Repository: backend/User/Adapters/user_repository.py

User Entity

From user.py:6-31:
class User(Base):
    """Entidad Usuario del sistema."""
    __tablename__ = "users"

    id = Column(String(50), primary_key=True, index=True)
    username = Column(String(100), unique=True, nullable=False, index=True)
    email = Column(String(255), unique=True, nullable=False, index=True)
    password_hash = Column(String(255), nullable=False)
    active = Column(Boolean, default=True, nullable=False)
    role_id = Column(Integer, ForeignKey("roles.id"), nullable=False)

    role = relationship("Role", lazy="joined")

    def to_dict(self):
        return {
            "id": self.id,
            "username": self.username,
            "email": self.email,
            "active": self.active,
            "role_id": self.role_id,
            "role_name": self.role.name if self.role else None,
        }

User Fields

FieldTypeDescriptionConstraints
idString(50)Unique user identifierPrimary key, auto-generated UUID
usernameString(100)Login usernameUnique, required, indexed
emailString(255)User email addressUnique, required, indexed
password_hashString(255)Hashed passwordRequired, never exposed in API
activeBooleanAccount statusDefault: true
role_idIntegerForeign key to roles tableRequired

Creating Users

Endpoint

POST /api/v1/users/

Required Permission

@require_role('admin')  # user_controller.py:62

Request

Headers
Content-Type: application/json
X-User-Role: admin
Body
{
  "username": "jdoe",
  "email": "[email protected]",
  "password": "SecurePass123!",
  "role_id": 2
}

Response

Success (201)
{
  "status": "success",
  "message": "Usuario 'jdoe' creado exitosamente.",
  "user": {
    "id": "a3d5f678-1234-5678-9abc-def012345678",
    "username": "jdoe",
    "email": "[email protected]",
    "active": true,
    "role_id": 2,
    "role_name": "gestor"
  }
}
Error (400)
{
  "status": "error",
  "message": "El nombre de usuario 'jdoe' ya está en uso."
}

Implementation

From user_controller.py:61-98:
@router.route('/', methods=['POST'])
@require_role('admin')
def create_user():
    """Crea un nuevo usuario. Solo admin. Envía email de bienvenida (mock)."""
    try:
        data = request.get_json()
        if not data:
            return jsonify({"status": "error", "message": "Body requerido."}), 400

        required = ['username', 'email', 'password', 'role_id']
        missing = [f for f in required if not data.get(f)]
        if missing:
            return jsonify({
                "status": "error", 
                "message": f"Campos requeridos: {', '.join(missing)}"
            }), 400

        db = next(get_db())
        service = _get_service(db)

        user = service.create_user(
            username=data['username'],
            email=data['email'],
            password=data['password'],
            role_id=int(data['role_id']),
        )

        # Enviar email de bienvenida (mock)
        email_provider.send_welcome(email=user.email, username=user.username)

        return jsonify({
            "status": "success",
            "message": f"Usuario '{user.username}' creado exitosamente.",
            "user": user.to_dict()
        }), 201
    except ValueError as ve:
        return jsonify({"status": "error", "message": str(ve)}), 400

Service Logic

From user_service.py:38-58:
def create_user(self, username: str, email: str, password: str, role_id: int) -> User:
    # Verificar duplicados
    if self.user_repo.get_by_username(username):
        raise ValueError(f"El nombre de usuario '{username}' ya está en uso.")
    if self.user_repo.get_by_email(email):
        raise ValueError(f"El email '{email}' ya está registrado.")

    # Verificar que el rol exista
    role = self.role_repo.get_by_id(role_id)
    if not role:
        raise ValueError(f"El rol con id {role_id} no existe.")

    user = User(
        id=str(uuid.uuid4()),
        username=username,
        email=email,
        password_hash=generate_password_hash(password),
        active=True,
        role_id=role_id,
    )
    return self.user_repo.create(user)

Validations

The username must be unique across the system. Duplicate usernames are rejected with error message.
Email addresses must be unique. Attempting to register a duplicate email returns an error.
The role_id must reference an existing role (1=admin, 2=gestor, 3=consultor). Invalid role IDs are rejected.
Passwords are hashed using werkzeug.security.generate_password_hash before storage. Plain text passwords are never stored.

Welcome Email

When a user is created, a welcome email is sent (currently mocked):
email_provider.send_welcome(email=user.email, username=user.username)
Email functionality is currently mocked for development. In production, this will send actual emails via the configured email provider.

Listing Users

Endpoint

GET /api/v1/users/

Required Permission

@require_role('admin')  # user_controller.py:23

Request

GET /api/v1/users/?skip=0&limit=100
X-User-Role: admin
Query Parameters:
  • skip (optional): Number of records to skip (default: 0)
  • limit (optional): Maximum records to return (default: 100)

Response

{
  "status": "success",
  "count": 3,
  "users": [
    {
      "id": "uuid-1",
      "username": "admin",
      "email": "[email protected]",
      "active": true,
      "role_id": 1,
      "role_name": "admin"
    },
    {
      "id": "uuid-2",
      "username": "jdoe",
      "email": "[email protected]",
      "active": true,
      "role_id": 2,
      "role_name": "gestor"
    },
    {
      "id": "uuid-3",
      "username": "viewer",
      "email": "[email protected]",
      "active": false,
      "role_id": 3,
      "role_name": "consultor"
    }
  ]
}
From user_controller.py:22-39

Getting User Details

Endpoint

GET /api/v1/users/<user_id>

Required Permission

@require_role('admin')  # user_controller.py:103

Request

GET /api/v1/users/a3d5f678-1234-5678-9abc-def012345678
X-User-Role: admin

Response

Success (200)
{
  "status": "success",
  "user": {
    "id": "a3d5f678-1234-5678-9abc-def012345678",
    "username": "jdoe",
    "email": "[email protected]",
    "active": true,
    "role_id": 2,
    "role_name": "gestor"
  }
}
Not Found (404)
{
  "status": "error",
  "message": "Usuario no encontrado."
}
From user_controller.py:102-115

Updating Users

Endpoint

PUT /api/v1/users/<user_id>

Required Permission

@require_role('admin')  # user_controller.py:120

Request

Headers
Content-Type: application/json
X-User-Role: admin
Body (all fields optional)
{
  "username": "john.doe",
  "email": "[email protected]",
  "role_id": 1,
  "password": "NewPassword123!",
  "active": true
}

Response

Success (200)
{
  "status": "success",
  "message": "Usuario actualizado correctamente.",
  "user": {
    "id": "a3d5f678-1234-5678-9abc-def012345678",
    "username": "john.doe",
    "email": "[email protected]",
    "active": true,
    "role_id": 1,
    "role_name": "admin"
  }
}

Implementation

From user_controller.py:119-146:
@router.route('/<user_id>', methods=['PUT'])
@require_role('admin')
def update_user(user_id):
    """Edita un usuario existente. Solo admin."""
    try:
        data = request.get_json()
        if not data:
            return jsonify({"status": "error", "message": "Body requerido."}), 400

        # Convertir role_id a int si viene en los datos
        if 'role_id' in data:
            data['role_id'] = int(data['role_id'])

        db = next(get_db())
        service = _get_service(db)
        user = service.update_user(user_id, data)

        return jsonify({
            "status": "success",
            "message": "Usuario actualizado correctamente.",
            "user": user.to_dict()
        }), 200
    except ValueError as ve:
        return jsonify({"status": "error", "message": str(ve)}), 400

Service Logic

From user_service.py:60-87:
def update_user(self, user_id: str, data: dict) -> User:
    user = self.user_repo.get_by_id(user_id)
    if not user:
        raise ValueError("Usuario no encontrado.")

    if "username" in data and data["username"] != user.username:
        if self.user_repo.get_by_username(data["username"]):
            raise ValueError(f"El nombre de usuario '{data['username']}' ya está en uso.")
        user.username = data["username"]

    if "email" in data and data["email"] != user.email:
        if self.user_repo.get_by_email(data["email"]):
            raise ValueError(f"El email '{data['email']}' ya está registrado.")
        user.email = data["email"]

    if "role_id" in data:
        role = self.role_repo.get_by_id(data["role_id"])
        if not role:
            raise ValueError(f"El rol con id {data['role_id']} no existe.")
        user.role_id = data["role_id"]

    if "password" in data and data["password"]:
        user.password_hash = generate_password_hash(data["password"])

    if "active" in data:
        user.active = bool(data["active"])

    return self.user_repo.update(user)

Update Validations

  • Username and email uniqueness are checked only if changed
  • Role ID must reference a valid role
  • Password is re-hashed if updated
  • All fields are optional - only provided fields are updated

Deactivating Users

Endpoint

PATCH /api/v1/users/<user_id>/deactivate

Required Permission

@require_role('admin')  # user_controller.py:151

Request

PATCH /api/v1/users/a3d5f678-1234-5678-9abc-def012345678/deactivate
X-User-Role: admin

Response

{
  "status": "success",
  "message": "Usuario 'jdoe' desactivado.",
  "user": {
    "id": "a3d5f678-1234-5678-9abc-def012345678",
    "username": "jdoe",
    "email": "[email protected]",
    "active": false,
    "role_id": 2,
    "role_name": "gestor"
  }
}

Implementation

From user_controller.py:150-167:
@router.route('/<user_id>/deactivate', methods=['PATCH'])
@require_role('admin')
def deactivate_user(user_id):
    """Desactiva un usuario. Solo admin."""
    try:
        db = next(get_db())
        service = _get_service(db)
        user = service.deactivate_user(user_id)
        return jsonify({
            "status": "success",
            "message": f"Usuario '{user.username}' desactivado.",
            "user": user.to_dict()
        }), 200
    except ValueError as ve:
        return jsonify({"status": "error", "message": str(ve)}), 400

Service Logic

From user_service.py:89-94:
def deactivate_user(self, user_id: str) -> User:
    user = self.user_repo.get_by_id(user_id)
    if not user:
        raise ValueError("Usuario no encontrado.")
    user.active = False
    return self.user_repo.update(user)
Deactivated users cannot log in. The authentication system checks the active status during login and rejects inactive accounts.

Reactivating Users

To reactivate a user, use the update endpoint:
PUT /api/v1/users/<user_id>
Content-Type: application/json
X-User-Role: admin

{
  "active": true
}

Role Assignment

Getting Available Roles

GET /api/v1/users/roles
X-User-Role: admin
Response:
{
  "status": "success",
  "roles": [
    {"id": 1, "name": "admin", "description": "Acceso total..."},
    {"id": 2, "name": "gestor", "description": "Gestiona productos..."},
    {"id": 3, "name": "consultor", "description": "Solo lectura..."}
  ]
}
From user_controller.py:43-57

Changing User Roles

Roles are assigned during user creation or changed via the update endpoint:
PUT /api/v1/users/<user_id>
Content-Type: application/json
X-User-Role: admin

{
  "role_id": 1
}
Role changes take effect immediately. Users must re-authenticate to receive updated permissions.

User Lifecycle

1

User Creation

Admin creates user account with username, email, password, and role assignment. System generates UUID and hashes password.
2

Welcome Email

System sends welcome email (mocked) with login credentials and getting started information.
3

First Login

User logs in with credentials. System validates account is active and returns user details with role.
4

Active Usage

User accesses system features based on assigned role permissions. All actions are logged to audit system.
5

Role Changes

Admin updates user role as responsibilities change. User must re-authenticate for new permissions.
6

Deactivation

When user leaves or account is compromised, admin deactivates account. User can no longer log in.
7

Reactivation (Optional)

If needed, admin can reactivate account by setting active=true. User regains access with existing credentials.

Common Operations

Create Manager Account

Create user with role_id: 2 (gestor) for inventory management staff.

Promote to Admin

Update existing user with role_id: 1 to grant full administrative access.

Reset Password

Update user with new password field. System will hash the new password automatically.

Temporary Deactivation

Use deactivate endpoint for temporary access suspension. Reactivate via update endpoint.

Error Handling

Error CodeScenarioMessage
400Missing required fields”Campos requeridos: username, email, password, role_id”
400Duplicate username”El nombre de usuario ‘jdoe’ ya está en uso.”
400Duplicate email”El email ‘[email protected]’ ya está registrado.”
400Invalid role ID”El rol con id 99 no existe.”
404User not found”Usuario no encontrado.”
401Missing auth header”Se requiere autenticación.”
403Insufficient permissions”El rol ‘gestor’ no tiene permiso.”
500Server error”Error interno del servidor”

Best Practices

Enforce strong password requirements: minimum 8 characters, uppercase, lowercase, numbers, and special characters.
Assign the minimum role required. Start with consultor and escalate only when necessary.
Periodically review user list and deactivate accounts that are no longer needed.
Use deactivation instead of deletion to maintain audit trail and data integrity.
Keep records of why and when user roles are changed for compliance and security.
Limit the number of admin accounts and monitor their activity closely.

API Quick Reference

MethodEndpointPermissionDescription
GET/api/v1/users/adminList all users
GET/api/v1/users/rolesadminList available roles
POST/api/v1/users/adminCreate new user
GET/api/v1/users/<id>adminGet user details
PUT/api/v1/users/<id>adminUpdate user
PATCH/api/v1/users/<id>/deactivateadminDeactivate user

Security Considerations

Password Storage: Passwords are hashed using werkzeug.security.generate_password_hash. Never log or expose password hashes in API responses.
Audit Logging: All user management operations are logged to the audit system with timestamps, admin user, and action details.
Default Admin: Change the default admin password (Admin1234!) immediately after initial setup.

Integration Examples

import requests

BASE_URL = "http://localhost:5000/api/v1"
headers = {"X-User-Role": "admin", "Content-Type": "application/json"}

# Create user
user_data = {
    "username": "jdoe",
    "email": "[email protected]",
    "password": "SecurePass123!",
    "role_id": 2
}
response = requests.post(f"{BASE_URL}/users/", json=user_data, headers=headers)
print(response.json())

# List users
response = requests.get(f"{BASE_URL}/users/", headers=headers)
print(response.json())

# Deactivate user
user_id = "a3d5f678-1234-5678-9abc-def012345678"
response = requests.patch(f"{BASE_URL}/users/{user_id}/deactivate", headers=headers)
print(response.json())

Next Steps

Authentication

Learn about the login and authentication system

Roles & Permissions

Understand role-based access control

Build docs developers (and LLMs) love