tengo esto:

Folder Structure:

FastAPI/
├─ requirements.txt
├─ Dockerfile
├─ app/
│   app/
│   ├─ alembic/
│   │   alembic/
│   │   ├─ env.py
│   │   ├─ README
│   │   ├─ script.py.mako
│   │   └─ versions/
│   │       versions/
│   │       └─ 905731c6a76a_first_migrations.py
│   ├─ 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
│   ├─ 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
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/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.Column('is_active', sa.Boolean(), nullable=True),
    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/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)
    is_active = Column(Boolean, default=True)




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), nullable=False)
    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


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,
    ) -> Task:
        """Creates a new task."""
        task = Task.create(
            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[Task]:
        """Retrieves a task by ID, checking user permissions."""
        task = Task.get_or_none(Task.id == task_id)
        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 = Task.get_or_none(Task.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():
                Change.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 = Task.get_or_none(Task.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 = Task.select().where((Task.user_id == user_id) | (is_admin))
        if status:
            query = query.where(Task.status_id == status)
        if expiration_date:
            query = query.where(Task.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 User

# Configure passlib context for password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


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

    @staticmethod
    def verify_password(plain_password: str, hashed_password: str) -> bool:
        """
        Verifies if the plain text password matches the hashed password.

        Args:
            plain_password (str): Plain text password.
            hashed_password (str): Hashed password.

        Returns:
            bool: True if they match, False otherwise.
        """
        return pwd_context.verify(plain_password, hashed_password)

    @staticmethod
    def get_password_hash(password: str) -> str:
        """
        Hashes a plain text password.

        Args:
            password (str): Plain text password.

        Returns:
            str: Hashed password.
        """
        return pwd_context.hash(password)

    @staticmethod
    def authenticate_user(email: str, password: str) -> Optional[User]:
        """
        Authenticates a user.

        Args:
            email (str): User's email.
            password (str): User's password.

        Returns:
            Optional[User]: Authenticated user if credentials are valid, otherwise None.
        """
        user = UserService.get_user_by_email(email)
        if not user:
            return None
        if not AuthService.verify_password(password, user.hashed_password):
            return None
        return user

    @staticmethod
    def create_access_token(
        data: dict, expires_delta: Optional[timedelta] = None
    ) -> str:
        """
        Creates a JWT access token.

        Args:
            data (dict): Data to encode in the token.
            expires_delta (Optional[timedelta]): Expiration time of the token.

        Returns:
            str: JWT token.
        """
        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) -> User:
        """
        Retrieves the current user from the JWT token.

        Args:
            token (str): JWT token.

        Returns:
            User: Authenticated user.

        Raises:
            HTTPException: If the token is invalid or the user does not exist.
        """
        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])
            email: str = payload.get("sub")
            if email is None:
                raise credentials_exception
        except JWTError as ve:
            raise credentials_exception from ve
        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 User
from models.role import Role
class UserService:
    """Service layer for User operations."""

    @staticmethod
    def create_user(email: str, password: str, role_id: int) -> User:
        """
        Create a new user.
        """
        try:
            role_instance = RoleModel.get_by_id(role_id)
            user_instance = UserModel.create(email=email, password=password, role=role_instance)
            created_user = User.model_validate(user_instance)
            created_user.role = Role.model_validate(role_instance)
            return created_user
        except DoesNotExist as exc:
            raise ValueError(f"Role with id {role_id} not found") from exc

    @staticmethod
    def get_user_by_id(user_id: int) -> Optional[User]:
        """
        Get a user by their ID.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            return User.model_validate(user_instance)  # Asegúrate de que `model_validate` es correcto
        except DoesNotExist:
            return None

@staticmethod
def get_user_by_email(email: str) -> Optional[User]:
    """
    Get a user by their email.
    """
    try:
        user_instance = UserModel.get(UserModel.email == email)
        
        # Convertir el usuario y su rol a diccionarios
        user_data = user_instance.__data__.copy()
        user_data['role'] = user_instance.role.__data__  # Convierte `role` a un diccionario
        
        # Validar el diccionario completo usando Pydantic
        return User.model_validate(user_data)
    except DoesNotExist:
        return None


    @staticmethod
    def update_user(
        user_id: int,
        email: Optional[str] = None,
        password: Optional[str] = None,
        role_id: Optional[int] = None,
    ) -> Optional[User]:
        """
        Update an existing user by their 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()
            return User.model_validate(user_instance)  # Asegúrate de que `model_validate` es correcto

        except DoesNotExist:
            return None

    @staticmethod
    def delete_user(user_id: int) -> bool:
        """
        Delete a user by their 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[User]:
        """
        Get a list of all users.
        """
        users = UserModel.select()
        return [User.model_validate(user) for user in users]  # Asegúrate de que `model_validate` es correcto



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, Field  # Añade Field aquí
from .role import Role

class User(BaseModel):
    """
    Represents a user with their credentials and associated role.
    """
    id: int
    email: EmailStr
    role_id: int = Field(alias='role')
    hashed_password: Optional[str] = Field(alias='password')
    is_active: bool
    role: Role

    class Config:  # pylint: disable=too-few-public-methods
        """
        Model config.
        """
        from_attributes = True

class UserCreate(BaseModel):
    """
    Model for creating a new user.
    """
    email: EmailStr
    password: str # Plaintext; hash before storing.
    role_id: int

class UserUpdate(BaseModel):
    """
    Model for updating an existing user.
    """
    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 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 starlette.responses import RedirectResponse
from routes import task_router, user_management_router, auth_routes

# Routers

from config.database import database as connection
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(api: FastAPI):  # pylint: disable=unused-argument
    """
    Manage the lifespan of the FastAPI application.

    Connects to the database at startup and closes the connection at shutdown.
    """

    # Connect to the database if the connection is closed
    if connection.is_closed():
        connection.connect()
    try:
        yield  # Application code runs here
    finally:
        #  close connection when the aplication is stop
        if not connection.is_closed:
            connection.close()


app = FastAPI(lifespan=lifespan)

# On Startup
# On Shutdown


# Documentation
@app.get("/")
async def docs():
    """
    Redirect to the documentation.
    """
    return RedirectResponse(url="/docs")


# Include routers for events and tickets
app.include_router(auth_routes.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/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 routes.auth_routes import router as auth_router
from routes.task_routes import router as task_router
from routes.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 datetime import timedelta
from typing import Optional
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, EmailStr
from models.user import User, UserCreate
from services.auth_service import AuthService
from services.user_service import UserService
from config.settings import ACCESS_TOKEN_EXPIRE_MINUTES

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


class Token(BaseModel):
    """
    Response model for JWT tokens.
    """
    access_token: str
    token_type: str


class TokenData(BaseModel):
    """
    Model for the data contained in the JWT token.
    """
    email: Optional[EmailStr] = None


@router.post("/register", response_model=User, status_code=status.HTTP_201_CREATED)
def register(user: UserCreate):
    """
    Endpoint for registering a new user.

    Args:
        user (UserCreate): User data to be registered.

    Returns:
        User: Registered user.

    Raises:
        HTTPException: If the email is already in use or there is an error during creation.
    """
    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(user: UserCreate):
    """
    Endpoint to authenticate a user and provide a JWT token.

    Args:
        user (UserCreate): User credentials.

    Returns:
        Token: JWT token for authentication.

    Raises:
        HTTPException: If the credentials are invalid.
    """
    user_auth = AuthService.authenticate_user(user.email, user.password)
    if not user_auth:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = AuthService.create_access_token(
        data={"sub": user_auth.email},
        expires_delta=access_token_expires
    )
    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 User
from models.task import Task, TaskUpdate
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(
    title: str,
    description: Optional[str] = None,
    expiration_date: Optional[date] = None,
    status_id: int = 1,  # Default 'To Do'
    current_user: User = Depends(get_current_user)
) -> Task:
    """
    Create a new task.
    """
    task = TaskService.create_task(
        title=title,
        description=description,
        expiration_date=expiration_date,
        status_id=status_id,
        user_id=current_user.id
    )
    return task

@router.get("/{task_id}", response_model=Task)
def get_task(
    task_id: int,
    current_user: User = 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
    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: User = Depends(get_current_user)
) -> 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: User = Depends(get_current_user)
):
    """
    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: User = Depends(get_current_user)
) -> 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: User = Depends(get_current_user)
) -> 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: User = Depends(get_current_user)
) -> 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 User, 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: User):
    """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[User])
def list_users() -> List[User]:
    """List all registered users."""
    return UserService.list_all_users()


@router.post("/", response_model=User, status_code=status.HTTP_201_CREATED)
def create_user(
    user: UserCreate,
    current_admin: User = Depends(get_current_admin)
) -> User:
    """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=User)
def get_user(
    user_id: int,
    current_admin: User = Depends(get_current_admin)
) -> User:
    """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=User)
def update_user(
    user_id: int,
    user_update: UserUpdate,
    current_admin: User = Depends(get_current_admin)
) -> User:
    """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: User = Depends(get_current_admin)
):
    """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: User = Depends(get_current_admin)
):
    """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
    user.save()
    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
from fastapi.security import OAuth2PasswordBearer

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

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")


def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
    """Retrieves the current user from the JWT token."""
    return AuthService.get_current_user(token)


def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
    """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: User = Depends(get_current_user)) -> User:
    """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.


pero me sale este error a la hora de registrar un usuario:

{
    "detail": "2 validation errors for User\nrole\n  Input should be a valid integer [type=int_type, input_value=<RoleModel: 1>, input_type=RoleModel]\n    For further information visit https://errors.pydantic.dev/2.9/v/int_type\nrole\n  Input should be a valid dictionary or instance of Role [type=model_type, input_value=<RoleModel: 1>, input_type=RoleModel]\n    For further information visit https://errors.pydantic.dev/2.9/v/model_type"
}

asi meto en el postman:

{
  "email": "johan@example.com",
  "password": "12121212",
  "role_id": 1
}