mira, tengo esto, necesito que me ayudes a hacer las pruebas de los endpoinst en postman:

Folder Structure:

FastAPI/
├─ requirements.txt
├─ Dockerfile
├─ app/
│   app/
│   ├─ alembic/
│   │   alembic/
│   │   ├─ env.py
│   │   ├─ README
│   │   ├─ script.py.mako
│   │   ├─ __pycache__/
│   │   │   __pycache__/
│   │   │   └─ env.cpython-312.pyc
│   │   └─ versions/
│   │       versions/
│   │       ├─ 905731c6a76a_first_migrations.py
│   │       └─ __pycache__/
│   │           __pycache__/
│   │           └─ 905731c6a76a_first_migrations.cpython-312.pyc
│   ├─ migrations.py
│   ├─ services/
│   │   services/
│   │   ├─ __init__.py
│   │   ├─ task_service.py
│   │   ├─ auth_service.py
│   │   ├─ __pycache__/
│   │   │   __pycache__/
│   │   │   ├─ auth_service.cpython-312.pyc
│   │   │   ├─ task_service.cpython-312.pyc
│   │   │   ├─ __init__.cpython-312.pyc
│   │   │   └─ user_service.cpython-312.pyc
│   │   └─ user_service.py
│   ├─ models/
│   │   models/
│   │   ├─ status.py
│   │   ├─ __init__.py
│   │   ├─ role.py
│   │   ├─ __pycache__/
│   │   │   __pycache__/
│   │   │   ├─ task.cpython-312.pyc
│   │   │   ├─ role.cpython-312.pyc
│   │   │   ├─ change.cpython-312.pyc
│   │   │   ├─ user.cpython-312.pyc
│   │   │   └─ __init__.cpython-312.pyc
│   │   ├─ user.py
│   │   ├─ change.py
│   │   └─ task.py
│   ├─ main.py
│   ├─ .env
│   ├─ __pycache__/
│   │   __pycache__/
│   │   ├─ main.cpython-312.pyc
│   │   └─ migrations.cpython-312.pyc
│   ├─ config/
│   │   config/
│   │   ├─ __init__.py
│   │   ├─ database.py
│   │   ├─ __pycache__/
│   │   │   __pycache__/
│   │   │   ├─ settings.cpython-312.pyc
│   │   │   ├─ database.cpython-312.pyc
│   │   │   └─ __init__.cpython-312.pyc
│   │   └─ settings.py
│   ├─ routes/
│   │   routes/
│   │   ├─ __init__.py
│   │   ├─ auth_routes.py
│   │   ├─ __pycache__/
│   │   │   __pycache__/
│   │   │   ├─ auth_routes.cpython-312.pyc
│   │   │   ├─ task_routes.cpython-312.pyc
│   │   │   ├─ user_management_routes.cpython-312.pyc
│   │   │   └─ __init__.cpython-312.pyc
│   │   ├─ task_routes.py
│   │   └─ user_management_routes.py
│   ├─ alembic.ini
│   └─ utils/
│       utils/
│       ├─ __init__.py
│       ├─ __pycache__/
│       │   __pycache__/
│       │   ├─ dependencies.cpython-312.pyc
│       │   └─ __init__.cpython-312.pyc
│       └─ dependencies.py
└─ alembic.ini


File Contents:

File: FastAPI/requirements.txt
==============================
alembic==1.13.3
annotated-types==0.7.0
anyio==4.6.0
astroid==3.3.5
black==24.10.0
cffi==1.17.1
click==8.1.7
cryptography==43.0.1
dill==0.3.9
dnspython==2.7.0
ecdsa==0.19.0
email_validator==2.2.0
fastapi==0.115.0
greenlet==3.1.1
h11==0.14.0
idna==3.10
isort==5.13.2
python-multipart==0.0.5

Mako==1.3.5
MarkupSafe==3.0.1
mccabe==0.7.0
mypy-extensions==1.0.0
packaging==24.1
passlib==1.7.4
pathspec==0.12.1
peewee==3.17.6
platformdirs==4.3.6
pyasn1==0.6.1
pycparser==2.22
pydantic==2.9.2
pydantic_core==2.23.4
pylint==3.3.1
PyMySQL==1.1.1
python-dotenv==1.0.1
python-jose==3.3.0
rsa==4.9
six==1.16.0
sniffio==1.3.1
SQLAlchemy==2.0.35
starlette==0.38.6
tomlkit==0.13.2
typing_extensions==4.12.2
uvicorn==0.31.0



File: FastAPI/Dockerfile
========================
FROM python:3.12

COPY ./app /app
COPY ./requirements.txt /app

WORKDIR /app

RUN apt-get update && apt-get upgrade -y && apt-get install -y mariadb-client && pip install mysqlclient

RUN pip install --upgrade pip
RUN pip install -r requirements.txt

CMD [ "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80" ]


File: FastAPI/app/alembic/env.py
================================
"""
Este módulo configura Alembic para manejar migraciones de base de datos.

Se encarga de establecer la conexión a la base de datos y definir
el comportamiento de las migraciones, tanto en modo 'offline' como 'online'.
"""

from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from migrations import Base, DATABASE_URL  # pylint: disable=import-error

# Este objeto de configuración de Alembic proporciona acceso a los
# valores dentro del archivo .ini en uso.
config = context.config  # pylint: disable=no-member
config.set_main_option("sqlalchemy.url", DATABASE_URL)

# Interpreta el archivo de configuración para la configuración de registro.
# Esta línea configura los registradores en esencia.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

# Agrega el objeto MetaData de tu modelo aquí para el soporte de 'autogenerar'
target_metadata = Base.metadata

def run_migrations_offline() -> None:
    """Ejecuta migraciones en modo 'offline'.

    Esto configura el contexto solo con una URL y no con un Engine,
    aunque un Engine es aceptable aquí también. Al omitir la creación
    del Engine, ni siquiera necesitamos que un DBAPI esté disponible.

    Las llamadas a context.execute() aquí emiten la cadena dada en
    la salida del script.
    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure( # pylint: disable=no-member
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction(): # pylint: disable=no-member
        context.run_migrations() # pylint: disable=no-member

def run_migrations_online() -> None:
    """Ejecuta migraciones en modo 'online'.

    En este escenario, necesitamos crear un Engine y asociar una
    conexión con el contexto.
    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(connection=connection, target_metadata=target_metadata) # pylint: disable=no-member

        with context.begin_transaction(): # pylint: disable=no-member
            context.run_migrations() # pylint: disable=no-member

if context.is_offline_mode(): # pylint: disable=no-member
    run_migrations_offline()
else:
    run_migrations_online()


File: FastAPI/app/alembic/README
================================
Contenido binario o no legible.


File: FastAPI/app/alembic/script.py.mako
========================================
Contenido binario o no legible.


File: FastAPI/app/alembic/__pycache__/env.cpython-312.pyc
=========================================================
Contenido binario o no legible.


File: FastAPI/app/alembic/versions/905731c6a76a_first_migrations.py
===================================================================
"""first_migrations

Revision ID: 905731c6a76a
Revises: 
Create Date: 2024-11-06 18:26:01.991411

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '905731c6a76a'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('role',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=50), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_role_id'), 'role', ['id'], unique=False)
    op.create_table('status',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=255), nullable=False),
    sa.CheckConstraint("name IN ('TO_DO', 'IN_PROGRESS', 'COMPLETED')", name='check_status_name'),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_status_id'), 'status', ['id'], unique=False)
    op.create_table('user',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('email', sa.String(length=100), nullable=False),
    sa.Column('password', sa.String(length=255), nullable=False),
    sa.Column('role_id', sa.Integer(), nullable=True),
    sa.Column('is_active', sa.Boolean(), nullable=True),
    sa.ForeignKeyConstraint(['role_id'], ['role.id'], ),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('email')
    )
    op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False)
    op.create_table('task',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('title', sa.String(length=100), nullable=False),
    sa.Column('description', sa.Text(), nullable=True),
    sa.Column('date_of_creation', sa.Date(), nullable=False),
    sa.Column('expiration_date', sa.Date(), nullable=True),
    sa.Column('status_id', sa.Integer(), nullable=False),
    sa.Column('user_id', sa.Integer(), nullable=False),
    sa.Column('is_favorite', sa.Boolean(), nullable=True),
    sa.CheckConstraint('expiration_date IS NULL OR expiration_date >= date_of_creation', name='check_expiration_date'),
    sa.ForeignKeyConstraint(['status_id'], ['status.id'], ),
    sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_task_id'), 'task', ['id'], unique=False)
    op.create_table('change',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('task_id', sa.Integer(), nullable=False),
    sa.Column('timestamp', sa.Date(), nullable=False),
    sa.Column('field_changed', sa.String(length=100), nullable=False),
    sa.Column('old_value', sa.Text(), nullable=True),
    sa.Column('new_value', sa.Text(), nullable=True),
    sa.ForeignKeyConstraint(['task_id'], ['task.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_change_id'), 'change', ['id'], unique=False)
    # ### end Alembic commands ###
    op.execute("""
    INSERT INTO status (name) 
    VALUES ('TO_DO'), ('IN_PROGRESS'), ('COMPLETED');
    """)
    
    # Insertar los roles por defecto
    op.execute("""
    INSERT INTO role (name) 
    VALUES ('Administrator'), ('User');
    """)


def downgrade() -> None:
    # Borrar los datos insertados durante la migración
    op.execute("""
    DELETE FROM status WHERE name IN ('TO_DO', 'IN_PROGRESS', 'COMPLETED');
    """)

    op.execute("""
    DELETE FROM role WHERE name IN ('Administrator', 'User');
    """)



def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(op.f('ix_change_id'), table_name='change')
    op.drop_table('change')
    op.drop_index(op.f('ix_task_id'), table_name='task')
    op.drop_table('task')
    op.drop_index(op.f('ix_user_id'), table_name='user')
    op.drop_table('user')
    op.drop_index(op.f('ix_status_id'), table_name='status')
    op.drop_table('status')
    op.drop_index(op.f('ix_role_id'), table_name='role')
    op.drop_table('role')
    # ### end Alembic commands ###



File: FastAPI/app/alembic/versions/__pycache__/905731c6a76a_first_migrations.cpython-312.pyc
============================================================================================
Contenido binario o no legible.


File: FastAPI/app/migrations.py
===============================
"""
Necessary imports to define data models using SQLAlchemy ORM.
- Column: Defines a column in a table.
- Integer: Integer data type for columns.
- String: Variable-length string data type.
- Text: Large text data type for columns.
- Date: Date data type for columns.
- Boolean: Boolean data type for columns.
- ForeignKey: Defines a foreign key constraint.
- CheckConstraint: Adds a check constraint to a column.
- declarative_base: Base class for declarative model definitions.
- relationship: Establishes relationships between models.
- sessionmaker: Factory for creating new Session objects.
- create_engine: Creates a new SQLAlchemy Engine instance.
"""
from sqlalchemy import Column, Integer, String, Text, Date, Boolean, ForeignKey, CheckConstraint
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy import create_engine
from config.settings import DATABASE

# Base declarative class for SQLAlchemy models
Base = declarative_base()

class Role(Base):# pylint: disable=too-few-public-methods
    """Role table for storing user roles."""
    __tablename__ = "role"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50),nullable=False)

class Status(Base):# pylint: disable=too-few-public-methods
    """Status table defining the various states for tasks."""
    __tablename__ = "status"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(255), nullable=False)

    __table_args__ = (
        CheckConstraint("name IN ('TO_DO', 'IN_PROGRESS', 'COMPLETED')", name="check_status_name"),
    )

class User(Base):  # pylint: disable=too-few-public-methods
    """User table for storing user information and their associated roles."""
    __tablename__ = "user"
    id = Column(Integer, primary_key=True, index=True)
    email = Column(String(100), unique=True, nullable=False)
    password = Column(String(255))  # Solo aquí se maneja hashed_password
    role_id = Column(Integer, ForeignKey('role.id'), nullable=True)
    role = relationship("Role", back_populates="users")
    is_active = Column(Boolean, default=True)


class Task(Base): # pylint: disable=too-few-public-methods
    """Task table for storing tasks with relationships to users and statuses."""
    __tablename__ = "task"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(100), nullable=False)
    description = Column(Text, nullable=True)
    date_of_creation = Column(Date, nullable=False)
    expiration_date = Column(Date, nullable=True)
    status_id = Column(Integer, ForeignKey('status.id'), nullable=False)
    user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
    is_favorite = Column(Boolean, default=False)

    status = relationship("Status", back_populates="tasks")
    user = relationship("User", back_populates="tasks")

    # Constraints for the table (comparison between columns)
    __table_args__ = (
        CheckConstraint(
            "expiration_date IS NULL OR expiration_date >= date_of_creation", 
            name="check_expiration_date"
        ),
    )

class Change(Base):# pylint: disable=too-few-public-methods
    """Change table for logging modifications to tasks."""
    __tablename__ = "change"
    id = Column(Integer, primary_key=True, index=True)
    task_id = Column(Integer, ForeignKey('task.id'), nullable=False)
    timestamp = Column(Date, nullable=False)
    field_changed = Column(String(100), nullable=False)
    old_value = Column(Text, nullable=True)
    new_value = Column(Text, nullable=True)

    task = relationship("Task", back_populates="changes")

# Configurar la base de datos
DATABASE_URL = (
    f"mysql+pymysql://{DATABASE['user']}:{DATABASE['password']}@"
    f"{DATABASE['host']}/{DATABASE['name']}"
)
engine = create_engine(DATABASE_URL)

# Crear una sesión de base de datos
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)



File: FastAPI/app/services/__init__.py
======================================



File: FastAPI/app/services/task_service.py
==========================================
"""
Module defining services for managing tasks.
"""

from datetime import date, datetime
from typing import List, Optional
from models.task import Task
from models.change import Change
from config.database import TaskModel, ChangeModel

class TaskService:
    """
    Module defining the services.
    """

    @staticmethod
    def create_task(
        title: str,
        description: Optional[str],
        expiration_date: Optional[date],
        status_id: int,
        user_id: int,
    ) -> TaskModel:  # Devuelve el modelo de la base de datos
        """Creates a new task."""
        task = TaskModel.create(  # Asegúrate de usar el modelo ORM aquí
            title=title,
            description=description,
            expiration_date=expiration_date,
            status_id=status_id,
            user_id=user_id,
        )
        return task

    @staticmethod
    def get_task_by_id(task_id: int, user_id: int, is_admin: bool) -> Optional[TaskModel]:
        """Retrieves a task by ID, checking user permissions."""
        task = TaskModel.get_or_none(TaskModel.id == task_id)  # Usa el modelo ORM aquí
        if task and (task.user_id == user_id or is_admin):
            return task
        return None
    
    @staticmethod
    def update_task(
        task_id: int, user_id: int, is_admin: bool, **updates
    ) -> Optional[Task]:
        """Updates a task, logging changes, and checking user permissions."""
        task = TaskModel.get_or_none(TaskModel.id == task_id)
        if task and (task.user_id == user_id or is_admin):
            for key, value in updates.items():
                setattr(task, key, value)
            task.save()

            # Log changes to the change history
            for field, new_value in updates.items():
                ChangeModel.create(
                    task=task,
                    timestamp=datetime.now(),
                    field_changed=field,
                    old_value=getattr(task, field, None),
                    new_value=new_value,
                )
            return task
        return None

    @staticmethod
    def delete_task(task_id: int, user_id: int, is_admin: bool) -> bool:
        """Deletes a task, checking user permissions."""
        task = TaskModel.get_or_none(TaskModel.id == task_id)
        if task and (task.user_id == user_id or is_admin):
            task.delete_instance()
            return True
        return False

    @staticmethod
    def list_tasks(
        user_id: int,
        status: Optional[str],
        expiration_date: Optional[date],
        is_admin: bool,
    ) -> List[Task]:
        """Lists tasks, applying filters and checking user permissions."""
        query = TaskModel.select().where((TaskModel.user_id == user_id) | (is_admin))
        if status:
            query = query.where(TaskModel.status_id == status)
        if expiration_date:
            query = query.where(TaskModel.expiration_date <= expiration_date)
        return list(query)

    @staticmethod
    def toggle_favorite(task_id: int, user_id: int, is_admin: bool) -> Optional[Task]:
        """Toggles the favorite status of a task, checking user permissions."""
        task = Task.get_or_none(Task.id == task_id)
        if task and (task.user_id == user_id or is_admin):
            task.is_favorite = not task.is_favorite
            task.save()
            return task
        return None

    @staticmethod
    def get_changes_by_task_id(task_id: int) -> List[Change]:
        """Retrieves changes for a specific task."""
        return list(
            Change.select()
            .where(Change.task == task_id)
            .order_by(Change.timestamp.desc())
        )



File: FastAPI/app/services/auth_service.py
==========================================
"""
Authentication Service for handling password hashing and JWT.

Includes functions for verifying passwords, hashing passwords,
authenticating users, and creating access tokens.
"""
from datetime import datetime, timedelta
from typing import Optional

from fastapi import HTTPException, status
from jose import JWTError, jwt
from passlib.context import CryptContext
from config.settings import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
from services.user_service import UserService
from models.user import UserRead  # Asegúrate de importar UserRead correctamente
from config.database import UserModel
# Configurar passlib context para password hashing

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

class AuthService:
    """Authentication Service."""

    @staticmethod
    def verify_password(plain_password: str, password: str) -> bool:
        """Verifica si la contraseña en texto plano coincide con la contraseña hasheada."""
        return pwd_context.verify(plain_password, password)

    @staticmethod
    def get_password_hash(password: str) -> str:
        """Hashea una contraseña en texto plano antes de almacenarla."""
        return pwd_context.hash(password)

    @staticmethod
    def authenticate_user(email: str, password: str) -> Optional[UserModel]:
        """
        Autentica a un usuario utilizando su email y contraseña.
        """
        user = UserService.get_user_by_email_login(email)
        if not user or not AuthService.verify_password(password, user.password):  # Usa `user.password`
            return None
        return user

    @staticmethod
    def create_access_token(
        data: dict, expires_delta: Optional[timedelta] = None
    ) -> str:
        """
        Crea un token de acceso JWT.
        """
        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 get_current_user(token: str) -> Optional[UserRead]:
        """
        Decodes the JWT token and retrieves the user based on the email.
        """
        credentials_exception = HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
        try:
            # Decodifica el token JWT
            payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
            email: str = payload.get("sub")
            if email is None:
                raise credentials_exception
        except JWTError:
            raise credentials_exception

        # Busca el usuario en la base de datos usando el email
        user = UserService.get_user_by_email(email)
        if user is None:
            raise credentials_exception
        return user


File: FastAPI/app/services/__pycache__/auth_service.cpython-312.pyc
===================================================================
Contenido binario o no legible.


File: FastAPI/app/services/__pycache__/task_service.cpython-312.pyc
===================================================================
Contenido binario o no legible.


File: FastAPI/app/services/__pycache__/__init__.cpython-312.pyc
===============================================================
Contenido binario o no legible.


File: FastAPI/app/services/__pycache__/user_service.cpython-312.pyc
===================================================================
Contenido binario o no legible.


File: FastAPI/app/services/user_service.py
==========================================
"""
Service layer for User operations.

This module contains the business logic for managing users.
It interacts with the `UserModel` and uses the `User` model from Pydantic for data validation.
"""

from typing import Optional, List
from peewee import DoesNotExist
from config.database import UserModel, RoleModel
from models.user import UserRead
from models.role import Role

class UserService:
    """Service layer for User operations."""

    @staticmethod
    def create_user(email: str, password: str, role_id: int) -> UserRead:
        """
        Crear un nuevo usuario.
        """
        try:
            role_instance = RoleModel.get_by_id(role_id)
            user_instance = UserModel.create(email=email, password=password, role=role_instance)
            
            # Preparar datos para Pydantic
            user_data = user_instance.__data__.copy()
            user_data['role_id'] = role_instance.id  # Añadir role_id explícitamente
            
            # Serializar el campo 'role' correctamente
            role_data = {
                "id": role_instance.id,
                "name": role_instance.name
            }
            user_data['role'] = Role.model_validate(role_data)  # Usar el modelo Pydantic Role
            
            return UserRead.model_validate(user_data)
        except DoesNotExist as exc:
            raise ValueError(f"Role with id {role_id} not found") from exc

    @staticmethod
    def get_user_by_email_login(email: str) -> Optional[UserModel]:
        """
        Recupera el usuario completo desde la base de datos por su email.
        """
        try:
            return UserModel.get(UserModel.email == email)
        except UserModel.DoesNotExist:
            return None


    @staticmethod
    def get_user_by_email(email: str) -> Optional[UserRead]:
        """
        Obtener un usuario por su email.
        """
        try:
            user_instance = UserModel.get(UserModel.email == email)
            user_data = user_instance.__data__.copy()
            user_data['role_id'] = user_instance.role.id  # Añadir role_id explícitamente
            
            # Serializar el campo 'role' correctamente
            role_instance = user_instance.role
            role_data = {
                "id": role_instance.id,
                "name": role_instance.name
            }
            user_data['role'] = Role.model_validate(role_data)  # Usar el modelo Pydantic Role
            
            return UserRead.model_validate(user_data)
        except DoesNotExist:
            return None

    @staticmethod
    def get_user_by_id(user_id: int) -> Optional[UserRead]:
        """
        Obtener un usuario por su ID.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            user_data = user_instance.__data__.copy()
            user_data['role_id'] = user_instance.role.id  # Añadir role_id explícitamente
            
            # Serializar el campo 'role' correctamente
            role_instance = user_instance.role
            role_data = {
                "id": role_instance.id,
                "name": role_instance.name
            }
            user_data['role'] = Role.model_validate(role_data)  # Usar el modelo Pydantic Role
            
            return UserRead.model_validate(user_data)
        except DoesNotExist:
            return None

    @staticmethod
    def update_user_status(user_id: int, is_active: bool) -> bool:
        """
        Actualiza el estado activo de un usuario.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            user_instance.is_active = is_active
            user_instance.save()
            return True
        except DoesNotExist:
            return False
        
    @staticmethod
    def update_user(
        user_id: int,
        email: Optional[str] = None,
        password: Optional[str] = None,
        role_id: Optional[int] = None,
    ) -> Optional[UserRead]:
        """
        Actualizar un usuario existente por su ID.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)

            if email:
                user_instance.email = email
            if password:
                user_instance.password = password
            if role_id:
                try:
                    role_instance = RoleModel.get_by_id(role_id)
                    user_instance.role = role_instance
                except DoesNotExist as exc:
                    raise ValueError(f"Role with id {role_id} not found") from exc

            user_instance.save()

            # Preparar datos para Pydantic
            user_data = user_instance.__data__.copy()
            user_data['role_id'] = user_instance.role.id
            user_data['role'] = user_instance.role

            return UserRead.model_validate(user_data)
        except DoesNotExist:
            return None

    @staticmethod
    def delete_user(user_id: int) -> bool:
        """
        Eliminar un usuario por su ID.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            user_instance.delete_instance()
            return True
        except DoesNotExist:
            return False

    @staticmethod
    def list_all_users() -> List[UserRead]:
        """
        Obtener una lista de todos los usuarios.
        """
        users = UserModel.select()
        user_list = []
        for user_instance in users:
            user_data = user_instance.__data__.copy()
            user_data['role_id'] = user_instance.role.id
            user_data['role'] = user_instance.role
            user_list.append(UserRead.model_validate(user_data))
        return user_list


File: FastAPI/app/models/status.py
==================================
"""
This module defines the Status class, which represents a status model for managing 
different statuses that can be assigned to tasks or other entities within an application.

The Status class includes attributes such as the unique identifier and the name of the status.

It is built using Pydantic's `BaseModel` to ensure data validation and type checking.
"""

from pydantic import BaseModel


class Status(BaseModel):
    """
    Represents a status with relevant details.

    Attributes:
    -----------
    id : int
        Unique identifier for the status.
    name : str
        The name of the status (e.g., 'pending', 'completed', 'in progress'),
        describing the current state of an entity.
    """

    id: int
    name: str



File: FastAPI/app/models/__init__.py
====================================



File: FastAPI/app/models/role.py
================================
"""
This module defines the Role class, which represents a role model used to manage 
different roles or permissions within an application.

The Role class includes attributes such as the role's unique identifier and name.

It is built using Pydantic's `BaseModel` to ensure data validation and type checking.
"""

from pydantic import BaseModel


class Role(BaseModel):
    """
    Represents a role with relevant details.

    Attributes:
    -----------
    id : int
        Unique identifier for the role.
    name : str
        The name of the role (e.g., 'admin', 'user'),
        representing the permissions associated with it.
    """

    id: int
    name: str



File: FastAPI/app/models/__pycache__/task.cpython-312.pyc
=========================================================
Contenido binario o no legible.


File: FastAPI/app/models/__pycache__/role.cpython-312.pyc
=========================================================
Contenido binario o no legible.


File: FastAPI/app/models/__pycache__/change.cpython-312.pyc
===========================================================
Contenido binario o no legible.


File: FastAPI/app/models/__pycache__/user.cpython-312.pyc
=========================================================
Contenido binario o no legible.


File: FastAPI/app/models/__pycache__/__init__.cpython-312.pyc
=============================================================
Contenido binario o no legible.


File: FastAPI/app/models/user.py
================================
from typing import Optional
from pydantic import BaseModel, EmailStr
from .role import Role

class UserRead(BaseModel):
    id: int
    email: EmailStr
    role_id: int
    is_active: bool
    role: Role

    class Config:
        from_attributes = True

class UserCreate(BaseModel):
    email: EmailStr
    password: str  # Contraseña en texto plano; se hashea antes de almacenar
    role_id: int

class UserUpdate(BaseModel):
    email: Optional[EmailStr] = None
    password: Optional[str] = None
    role_id: Optional[int] = None


File: FastAPI/app/models/change.py
==================================
"""
Model for logging changes to tasks.
"""

from datetime import datetime
from typing import Optional
from pydantic import BaseModel


class Change(BaseModel):
    """
    Represents a record of a change made to a task.

    Attributes:
        id (int): Unique identifier of the change.
        task_id (int): ID of the task associated with the change.
        timestamp (datetime): Date and time when the change was made.
        field_changed (str): Field that was changed.
        old_value (Optional[str]): Previous value of the field.
        new_value (Optional[str]): New value of the field.
    """
    id: int
    task_id: int
    timestamp: datetime
    field_changed: str
    old_value: Optional[str] = None
    new_value: Optional[str] = None

    class Config: # pylint: disable=too-few-public-methods
        """
        Model Config
        """
        orm_mode = True



File: FastAPI/app/models/task.py
================================
"""
This module defines the Task class, which represents a task model for handling 
task-related data within an application.

The Task class includes attributes such as title, description, creation date, 
expiration date, and flags for tracking the status and importance of the task.

It is based on the Pydantic `BaseModel` to ensure data validation and type checking.
"""

from datetime import date
from typing import List, Optional
from pydantic import BaseModel
from .change import Change


class Task(BaseModel):
    """
    Represents a task with relevant details.

    Attributes:
    -----------
    id : int
        Unique identifier for the task.
    title : str
        Title of the task.
    description : str
        Detailed description of the task.
    date_of_creation : date
        The date when the task was created.
    expiration_date : date
        The deadline or expiration date for the task.
    status_id : int
        The status identifier, representing the current state
        of the task (e.g., completed, pending).
    user_id : int
        The identifier of the user associated with the task.
    is_favorite : bool
        Flag indicating whether the task is marked as a favorite.
    """

    id: int
    title: str
    description: str
    date_of_creation: date
    expiration_date: date
    status_id: int
    user_id: int
    is_favorite: bool
    changes: Optional[List[Change]] = None

    class Config:
        from_attributes = True  # Cambia 'orm_mode' a 'from_attributes' para Pydantic v2
        
class TaskCreate(BaseModel):
    title: str
    description: Optional[str] = None
    expiration_date: Optional[date] = None
    status_id: int

    
class TaskUpdate(BaseModel):
    """
    This is so that the task can be updated.
    """
    title: Optional[str] = None
    description: Optional[str] = None
    expiration_date: Optional[date] = None
    status_id: Optional[int] = None
    is_favorite: Optional[bool] = None



File: FastAPI/app/main.py
=========================
"""
Main module for the FastAPI application.
"""

from contextlib import asynccontextmanager
from fastapi import FastAPI
from starlette.responses import RedirectResponse
from routes import auth_router, task_router, user_management_router
from config.database import database as connection

@asynccontextmanager
async def lifespan(api: FastAPI):
    if connection.is_closed():
        connection.connect()
    try:
        yield
    finally:
        if not connection.is_closed():
            connection.close()

app = FastAPI(lifespan=lifespan)

@app.get("/")
async def docs_redirect():
    """Redirige a la documentación de la API."""
    return RedirectResponse(url="/docs")

app.include_router(auth_router)
app.include_router(task_router)
app.include_router(user_management_router)


File: FastAPI/app/.env
======================
Contenido binario o no legible.


File: FastAPI/app/__pycache__/main.cpython-312.pyc
==================================================
Contenido binario o no legible.


File: FastAPI/app/__pycache__/migrations.cpython-312.pyc
========================================================
Contenido binario o no legible.


File: FastAPI/app/config/__init__.py
====================================



File: FastAPI/app/config/database.py
====================================
"""
Necessary imports to define data models using Peewee ORM.
- Model: Base class for all models.
- CharField, TextField, DateField, etc.: Database field types.
- ForeignKeyField: Defines relationships between tables.
- Check: Defines column-level constraints.
- AutoField: Auto-incrementing field used as a primary key.
- fn: Provides SQL functions like CURRENT_TIMESTAMP.
"""
from datetime import date
from dotenv import load_dotenv
from config.settings import DATABASE
from peewee import (
    MySQLDatabase, Model, CharField, TextField, DateField,
    ForeignKeyField, BooleanField, TimestampField, Check, AutoField, fn, SQL
)

# Load environment variables from the .env file
load_dotenv()

# Create a MySQL database instance using environment variables
database = MySQLDatabase(
    DATABASE["name"],
    user=DATABASE["user"],
    passwd=DATABASE["password"],
    host=DATABASE["host"],
    port=DATABASE["port"],
)

class RoleModel(Model):
    """Role table."""
    id = AutoField(primary_key=True)
    name = CharField(null=False)

    class Meta:# pylint: disable=too-few-public-methods
        """Meta information for the Role model."""
        table_name = "role"
        database = database


class StatusModel(Model):
    """Status table with constraints on the name field."""
    id = AutoField(primary_key=True)
    name = CharField(
        null=False,
        constraints=[Check(SQL('"name" IN (\'TO_DO\', \'IN_PROGRESS\', \'COMPLETED\')'))]
    )

    class Meta:# pylint: disable=too-few-public-methods
        """Meta information for the Status model."""
        table_name = "status"
        database = database


class UserModel(Model):
    """User table with a relation to Role."""
    id = AutoField(primary_key=True)
    email = CharField(unique=True, null=False)
    password = CharField(null=False)
    role = ForeignKeyField(RoleModel, backref='users', null=True, on_delete='SET NULL')
    is_active = BooleanField(default=True)

    class Meta:# pylint: disable=too-few-public-methods
        """Meta information for the User model."""
        table_name = "user"
        database = database


class TaskModel(Model):
    """Task table with relationships to User and Status."""
    id = AutoField(primary_key=True)
    title = CharField(null=False)
    description = TextField(null=True)
    date_of_creation = DateField(default=date.today, null=False)
    expiration_date = DateField(null=True)
    status = ForeignKeyField(StatusModel, backref='tasks', on_delete='CASCADE')
    user = ForeignKeyField(UserModel, backref='tasks', on_delete='CASCADE')
    is_favorite = BooleanField(default=False)

    class Meta:# pylint: disable=too-few-public-methods
        """Meta information for the Task model."""
        table_name = "task"
        database = database
        constraints = [
            Check('expiration_date IS NULL OR expiration_date >= date_of_creation')
        ]


class ChangeModel(Model):
    """Change table to log modifications in Task."""
    id = AutoField(primary_key=True)
    task = ForeignKeyField(TaskModel, backref='changes', on_delete='CASCADE')
    timestamp = TimestampField(default=fn.CURRENT_TIMESTAMP)
    field_changed = CharField(null=False)
    old_value = TextField(null=True)
    new_value = TextField(null=True)

    class Meta:# pylint: disable=too-few-public-methods
        """Meta information for the Change model."""
        table_name = "change"
        database = database



File: FastAPI/app/config/__pycache__/settings.cpython-312.pyc
=============================================================
Contenido binario o no legible.


File: FastAPI/app/config/__pycache__/database.cpython-312.pyc
=============================================================
Contenido binario o no legible.


File: FastAPI/app/config/__pycache__/__init__.cpython-312.pyc
=============================================================
Contenido binario o no legible.


File: FastAPI/app/config/settings.py
====================================
"""
This module loads the environment configuration for the application.
Uses `dotenv` to load environment variables from a `.env` file.  
Defines the database configuration based on the application's environment.

Variables:
    ENV (str): Specifies the application environment. Defaults to 'dev' if not set.
    DATABASE (dict): Dictionary containing the database configuration.
        - name: Name of the database.
        - engine: Database engine used.
        - user: Username for the database connection.
        - password: Password for the database connection.
        - host: Host address of the database.
        - port: Port used for the database connection.
"""

import os
from dotenv import load_dotenv

# Load environment variables from the .env file.
load_dotenv()

# Set environment to 'dev' by default if 'ENV' is not provided.
ENV = os.getenv("ENV", "dev")

if ENV == "production":
    DATABASE = {
        "name": os.getenv("MYSQL_DATABASE"),
        "engine": "peewee.MySQLDatabase",  # MySQL as the database engine
        "user": os.getenv("MYSQL_USER"),
        "password": os.getenv("MYSQL_PASSWORD"),
        "host": os.getenv("MYSQL_HOST"),
        "port": int(os.getenv("MYSQL_PORT")),
    }
else:
    DATABASE = {
        "name": os.getenv("MYSQL_DATABASE"),
        "engine": "peewee.MySQLDatabase",  # MySQL as the database engine
        "user": os.getenv("MYSQL_USER"),
        "password": os.getenv("MYSQL_PASSWORD"),
        "host": os.getenv("MYSQL_HOST"),
        "port": int(os.getenv("MYSQL_PORT")),
    }

# Configuración de JWT
SECRET_KEY = os.getenv("SECRET_KEY")
ALGORITHM = os.getenv("ALGORITHM", "HS256")
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30"))



File: FastAPI/app/routes/__init__.py
====================================
"""
init.py
"""
from .auth_routes import router as auth_router
from .task_routes import router as task_router
from .user_management_routes import router as user_management_router

__all__ = ["auth_router", "task_router", "user_management_router"]


File: FastAPI/app/routes/auth_routes.py
=======================================
"""
Module defining authentication routes.

Includes endpoints for user registration and login,
providing JWT tokens for authentication.
"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Form
from pydantic import BaseModel, EmailStr
from datetime import timedelta
from config.settings import ACCESS_TOKEN_EXPIRE_MINUTES
from models.user import UserRead, UserCreate
from services.auth_service import AuthService
from services.user_service import UserService
from utils.dependencies import OAuth2PasswordRequestFormEmail 


router = APIRouter(
    tags=["Authentication"],
)

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    email: Optional[EmailStr] = None

@router.post("/register", response_model=UserRead, status_code=status.HTTP_201_CREATED)
def register(user: UserCreate):
    existing_user = UserService.get_user_by_email(user.email)
    if existing_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered",
        )
    hashed_password = AuthService.get_password_hash(user.password)
    try:
        new_user = UserService.create_user(
            email=user.email,
            password=hashed_password,
            role_id=user.role_id
        )
        return new_user
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e),
        ) from e
    
@router.post("/login", response_model=Token)
def login(form_data: OAuth2PasswordRequestFormEmail = Depends()):
    # Autenticación usando el email
    user = AuthService.authenticate_user(form_data.email, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token = AuthService.create_access_token(
        data={"sub": user.email}  # Guarda el email en el token
    )
    return {"access_token": access_token, "token_type": "bearer"}


File: FastAPI/app/routes/__pycache__/auth_routes.cpython-312.pyc
================================================================
Contenido binario o no legible.


File: FastAPI/app/routes/__pycache__/task_routes.cpython-312.pyc
================================================================
Contenido binario o no legible.


File: FastAPI/app/routes/__pycache__/user_management_routes.cpython-312.pyc
===========================================================================
Contenido binario o no legible.


File: FastAPI/app/routes/__pycache__/__init__.cpython-312.pyc
=============================================================
Contenido binario o no legible.


File: FastAPI/app/routes/task_routes.py
=======================================
"""
Module defining routes for managing tasks.

This module uses FastAPI to define routes that allow
creating, reading, updating, and deleting tasks through a REST API.
"""
from datetime import date
from typing import List, Optional
from fastapi import APIRouter, HTTPException, Depends, status
from models.user import UserRead
from models.task import Task, TaskUpdate, TaskCreate
from models.change import Change
from services.task_service import TaskService
from utils.dependencies import get_current_user

router = APIRouter(
    prefix="/tasks",
    tags=["tasks"],
)
@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(
    task_data: TaskCreate,
    current_user: UserRead = Depends(get_current_user)
) -> Task:
    task_model = TaskService.create_task(
        title=task_data.title,
        description=task_data.description,
        expiration_date=task_data.expiration_date,
        status_id=task_data.status_id,
        user_id=current_user.id
    )
    # Convierte el modelo ORM a un modelo Pydantic
    return Task.from_orm(task_model)



@router.get("/{task_id}", response_model=Task)
def get_task(
    task_id: int,
    current_user: UserRead = Depends(get_current_user)
) -> Task:
    """
    Get a task by its ID.
    """
    is_admin = current_user.role.name == "Administrator"
    task = TaskService.get_task_by_id(task_id, user_id=current_user.id, is_admin=is_admin)
    if task:
        return Task.from_orm(task)  # Convierte de ORM a modelo Pydantic
    raise HTTPException(status_code=404, detail="Task not found")

@router.put("/{task_id}", response_model=Task)
def update_task(
    task_id: int,
    task_update: TaskUpdate,
    current_user: UserRead = Depends(get_current_user)  # Cambiado de User a UserRead
) -> Task:
    """Update an existing task."""
    is_admin = current_user.role.name == "Administrator"
    task = TaskService.update_task(
        task_id=task_id,
        user_id=current_user.id,
        is_admin=is_admin,
        **task_update.model_dump(exclude_unset=True)
    )
    if task:
        return task
    raise HTTPException(status_code=404, detail="Task not found")

@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_task(
    task_id: int,
    current_user: UserRead = Depends(get_current_user)  # Cambiado de User a UserRead
):
    """
    Delete a task by its ID.
    """
    is_admin = current_user.role.name == "Administrator"
    success = TaskService.delete_task(
        task_id=task_id,
        user_id=current_user.id,
        is_admin=is_admin
    )
    if success:
        return
    raise HTTPException(status_code=404, detail="Task not found")

@router.get("/", response_model=List[Task])
def list_tasks(
    task_status: Optional[str] = None,
    expiration_date: Optional[date] = None,
    current_user: UserRead = Depends(get_current_user)  # Cambiado de User a UserRead
) -> List[Task]:
    """
    List all tasks for the user with filtering options.
    """
    is_admin = current_user.role.name == "Administrator"
    tasks = TaskService.list_tasks(
        user_id=current_user.id,
        status=task_status,
        expiration_date=expiration_date,
        is_admin=is_admin
    )
    return tasks

@router.patch("/{task_id}/favorite", response_model=Task)
def toggle_favorite(
    task_id: int,
    current_user: UserRead = Depends(get_current_user)  # Cambiado de User a UserRead
) -> Task:
    """
    Toggle the favorite status of a task.
    """
    is_admin = current_user.role.name == "Administrator"
    task = TaskService.toggle_favorite(
        task_id=task_id,
        user_id=current_user.id,
        is_admin=is_admin
    )
    if task:
        return task
    raise HTTPException(status_code=404, detail="Task not found")

@router.get("/{task_id}/changes", response_model=List[Change])
def get_task_changes(
    task_id: int,
    current_user: UserRead = Depends(get_current_user)  # Cambiado de User a UserRead
) -> List[Change]:
    """
    Get the change history of a specific task.
    """
    is_admin = current_user.role.name == "Administrator"
    task = TaskService.get_task_by_id(task_id, user_id=current_user.id, is_admin=is_admin)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")

    changes = TaskService.get_changes_by_task_id(task_id)
    return [Change.from_orm(change) for change in changes]


File: FastAPI/app/routes/user_management_routes.py
==================================================
"""
Module that defines routes for managing users.

This module uses FastAPI to define the routes that allow
creating, reading, updating, and deleting users through a REST API.
"""
from typing import List
from fastapi import APIRouter, HTTPException, Depends, status
from models.user import UserRead, UserCreate, UserUpdate
from services.user_service import UserService
from utils.dependencies import get_current_admin

router = APIRouter(
    prefix="/admin/users",
    tags=["admin users"],
)

# Helper function to verify admin permissions
def verify_admin(current_admin: UserRead):  # Cambiar tipo a UserRead
    """Verifies if the user is an administrator."""
    if current_admin.role.name != "Administrator":
        raise HTTPException(status_code=403, detail="Insufficient permissions")

@router.get("/", response_model=List[UserRead])
def list_users() -> List[UserRead]:
    """List all registered users."""
    return UserService.list_all_users()

@router.post("/", response_model=UserRead, status_code=status.HTTP_201_CREATED)
def create_user(
    user: UserCreate,
    current_admin: UserRead = Depends(get_current_admin)  # Cambiar tipo a UserRead
) -> UserRead:
    # """Create a new user (for administrators only)."""
    # verify_admin(current_admin)

    try:
        created_user = UserService.create_user(
            email=user.email, password=user.password, role_id=user.role_id
        )
        return created_user
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e)) from e
    except Exception as e:
        raise HTTPException(status_code=500, detail="Error creating user") from e

@router.get("/{user_id}", response_model=UserRead)
def get_user(
    user_id: int,
    current_admin: UserRead = Depends(get_current_admin)  # Cambiar tipo a UserRead
) -> UserRead:
    """Get user information by their ID (for administrators only)."""
    verify_admin(current_admin)

    user = UserService.get_user_by_id(user_id)
    if user:
        return user
    raise HTTPException(status_code=404, detail="User not found")

@router.put("/{user_id}", response_model=UserRead)
def update_user(
    user_id: int,
    user_update: UserUpdate,
    current_admin: UserRead = Depends(get_current_admin)  # Cambiar tipo a UserRead
) -> UserRead:
    """Update existing user information (for administrators only)."""
    verify_admin(current_admin)

    updates = {}
    if user_update.email is not None:
        updates['email'] = user_update.email
    if user_update.password is not None:
        updates['password'] = user_update.password  # Hash the password before calling
    if user_update.role_id is not None:
        updates['role_id'] = user_update.role_id

    if not updates:
        raise HTTPException(status_code=400, detail="No data to update")

    try:
        user = UserService.update_user(
            user_id=user_id,
            email=updates.get("email"),
            password=updates.get("password"),
            role_id=updates.get("role_id")
        )
        if user:
            return user
        raise HTTPException(status_code=404, detail="User not found")
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e)) from e

@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(
    user_id: int,
    current_admin: UserRead = Depends(get_current_admin)  # Cambiar tipo a UserRead
):
    """Deletes a user by their ID (admin only)."""
    verify_admin(current_admin)

    success = UserService.delete_user(user_id)
    if success:
        return
    raise HTTPException(status_code=404, detail="User not found")

@router.patch("/{user_id}/active")
def toggle_user_active(
    user_id: int,
    current_admin: UserRead = Depends(get_current_admin)  # Cambiar tipo a UserRead
):
    """Activate or deactivate a user (for administrators only)."""
    verify_admin(current_admin)

    user = UserService.get_user_by_id(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    user.is_active = not user.is_active
    # Ya que UserRead es de solo lectura, necesitas actualizar el modelo ORM directamente
    UserService.update_user_status(user_id, user.is_active)
    return {"message": f"User {user_id} is now {'active' if user.is_active else 'inactive'}"}


File: FastAPI/app/alembic.ini
=============================
Contenido binario o no legible.


File: FastAPI/app/utils/__init__.py
===================================



File: FastAPI/app/utils/__pycache__/dependencies.cpython-312.pyc
================================================================
Contenido binario o no legible.


File: FastAPI/app/utils/__pycache__/__init__.cpython-312.pyc
============================================================
Contenido binario o no legible.


File: FastAPI/app/utils/dependencies.py
=======================================
"""
Security dependencies for authentication and authorization handling.
Includes functions to obtain the current user and verify roles.
"""
from fastapi import Depends, HTTPException, status, Form
from fastapi.security import OAuth2PasswordBearer

from models.user import UserRead
from services.auth_service import AuthService

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")

class OAuth2PasswordRequestFormEmail:
    def __init__(self, email: str = Form(...), password: str = Form(...)):
        self.email = email
        self.password = password

def get_current_user(token: str = Depends(oauth2_scheme)) -> UserRead:
    """Retrieves the current user from the JWT token."""
    user = AuthService.get_current_user(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user


def get_current_active_user(current_user: UserRead = Depends(get_current_user)) -> UserRead:
    """Verifies that the user is active."""
    if not current_user.is_active:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


def get_current_admin(current_user: UserRead = Depends(get_current_user)) -> UserRead:
    """Verifies if the user is an administrator."""
    if current_user.role.name != "Administrator":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Access forbidden: Administrator access required"
        )
    return current_user


File: FastAPI/alembic.ini
=========================
Contenido binario o no legible.