mira, este es el proyecto que hay que relizarse:

La idea es desarrollar un sistema en el cual los usuarios puedan registrar, editar, eliminar y visualizar tareas (por ejemplo, una aplicación de lista de tareas o “To-Do List”). Aquí están los requerimientos detallados:

Requerimientos Funcionales


Registro e inicio de sesión de usuarios:
•    Los usuarios deben poder registrarse con un correo electrónico y una contraseña.
•    Debe implementarse una funcionalidad para el inicio de sesión con autenticación JWT (JSON Web Tokens).
•    Se deben manejar roles: “Usuario normal” y “Administrador”.
Gestión de tareas:
•    Crear tareas: Cada usuario debe poder crear nuevas tareas que contengan al menos: título, descripción, fecha de vencimiento y estado (por hacer, en progreso, completado).
•    Editar tareas: Los usuarios pueden modificar la información de una tarea existente.
•    Eliminar tareas: Los usuarios pueden eliminar tareas específicas.
•    Listar tareas: Debe existir una vista para que el usuario vea todas sus tareas, con la opción de filtrarlas por estado y fecha de vencimiento.
Control de acceso basado en roles (RBAC):
•    Los usuarios normales solo pueden ver y gestionar sus propias tareas.
•    Los administradores tienen la capacidad de ver y gestionar las tareas de todos los usuarios.
Gestión de usuarios (solo para administradores):
•    Los administradores deben poder listar todos los usuarios registrados, así como editar y eliminar usuarios.
Funcionalidades adicionales:
•    Historial de cambios: Mantener un registro de los cambios realizados a las tareas (creación, edición, cambio de estado).
•    Marcar tareas como “Favoritas”: Los usuarios pueden marcar algunas tareas como favoritas para resaltarlas en la lista.

como tal tengo esto en mi proyecto:

Folder Structure:

To-do-List/
├─ .gitignore
├─ docker-compose.yml
├─ FastAPI/
│   FastAPI/
│   ├─ Dockerfile
│   ├─ requirements.txt
│   └─ app/
│       app/
│       ├─ database.py
│       ├─ __init__.py
│       ├─ config/
│       │   config/
│       │   ├─ database.py
│       │   ├─ __init__.py
│       │   └─ settings.py
│       ├─ services/
│       │   services/
│       │   ├─ role_service.py
│       │   ├─ _init_.py
│       │   ├─ task_service.py
│       │   ├─ user_service.py
│       │   ├─ auth_service.py
│       │   └─ status_service.py
│       ├─ utils/
│       │   utils/
│       │   ├─ __init__.py
│       │   └─ security.py
│       ├─ models/
│       │   models/
│       │   ├─ status.py
│       │   ├─ enums.py
│       │   ├─ _init_.py
│       │   ├─ task.py
│       │   ├─ token.py
│       │   ├─ role.py
│       │   ├─ user.py
│       │   └─ change.py
│       ├─ dependencies.py
│       ├─ routes/
│       │   routes/
│       │   ├─ _init_.py
│       │   ├─ auth_routes.py
│       │   ├─ user_routes.py
│       │   ├─ role_routes.py
│       │   ├─ task_routes.py
│       │   └─ status_routes.py
│       ├─ init_statuses.py
│       └─ main.py
├─ MySQL/
│   MySQL/
│   └─ Dockerfile
└─ .github/
    .github/
    └─ workflows/
        workflows/
        ├─ deploy.yml
        └─ pylint.yml


File Contents:

File: To-do-List/.gitignore
===========================
Contenido binario o no legible.


File: To-do-List/docker-compose.yml
===================================
services:
  db:
    build: 
      context: ./MySQL
      dockerfile: Dockerfile
    container_name: database
    restart: always
    ports:
      - 3306:3306
    volumes:
      - ./MySQL/volumes:/var/lib/mysql
    hostname: to-do-list-database
    networks:
      - net_to-do-list
    healthcheck:
        test: ["CMD","mysql", "-u", "user", "-proot"]
        interval: 30s
        timeout: 5s
        retries: 5
# --------------------------------------------------------------------
  # - The adminer is a tool for managing content in databases.
  adminer:
    image: adminer
    container_name: adminer-to-do-list
    restart: always
    ports:
      - 8080:8080
    networks:
      - net_to-do-list
# --------------------------------------------------------------------
  # - fastapi refers to the container where the application backend will be stored.
  fastapi:
    build: 
      context: ./FastAPI
      dockerfile: Dockerfile
    container_name: backend
    restart: always
    volumes:
      - ./FastAPI/app:/app 
    ports:
      - "8000:8000"
    command: uvicorn main:app --reload --host 0.0.0.0 --port 80  # Recarga automática con --reload
    depends_on:
      db:
        condition: service_healthy
    networks:
      - net_to-do-list
# --------------------------------------------------------------------

networks:
  net_to-do-list:
      driver: bridge
      name: net_to-do-list



File: To-do-List/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: To-do-List/FastAPI/requirements.txt
=========================================
alembic==1.13.3
annotated-types==0.7.0
anyio==4.6.0
astroid==3.3.5
black==24.10.0
click==8.1.7
dill==0.3.9
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
pathspec==0.12.1
peewee==3.17.6
platformdirs==4.3.6
pydantic==2.9.2
pydantic_core==2.23.4
pylint==3.3.1
PyMySQL==1.1.1
python-dotenv==1.0.1
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

passlib==1.7.4
python-jose==3.3.0


File: To-do-List/FastAPI/app/database.py
========================================
"""
Module for database configuration and models.
"""

import os
from dotenv import load_dotenv
from peewee import MySQLDatabase


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

database = MySQLDatabase(
    os.getenv("MYSQL_DATABASE"),
    user=os.getenv("MYSQL_USER"),
    passwd=os.getenv("MYSQL_PASSWORD"),
    host=os.getenv("MYSQL_HOST"),
    port=int(os.getenv("MYSQL_PORT")),
)



File: To-do-List/FastAPI/app/__init__.py
========================================



File: To-do-List/FastAPI/app/config/database.py
===============================================
"""
This module defines the database models using Peewee ORM for the application. 
It establishes a connection to a MySQL database using the configuration settings 
loaded from environment variables.

The following models are defined:

1. **BaseModel**: A base class for all models, specifying the database connection.
2. **RoleModel**: Represents the user roles with an auto-incrementing primary key.
3. **UserModel**: Represents users with fields for email, password, and a foreign key to RoleModel.
4. **StatusModel**: Represents task statuses with an auto-incrementing primary key.
5. **TaskModel**: Represents tasks with fields for title,
    description, creation and expiration dates, foreign keys to UserModel and 
    StatusModel, and a favorite status.
6. **ChangeModel**: Tracks changes made to tasks, including the timestamp of the change, 
   the field that was changed, and the old and new values.

Dependencies:
-------------
- dotenv: For loading environment variables.
- peewee: ORM for interacting with the MySQL database.
"""

from dotenv import load_dotenv  # pylint: disable=import-error
from config.settings import DATABASE  # pylint: disable=import-error
from peewee import ( # pylint: disable=import-error
    MySQLDatabase, # pylint: disable=import-error
    Model, # pylint: disable=import-error
    AutoField, # pylint: disable=import-error
    CharField, # pylint: disable=import-error
    ForeignKeyField, # pylint: disable=import-error
    DateField, # pylint: disable=import-error
    TextField, # pylint: disable=import-error
    BooleanField, # pylint: disable=import-error
    DateTimeField, # pylint: disable=import-error
)  # pylint: disable=import-error

load_dotenv()

database = MySQLDatabase(
    DATABASE["name"],
    user=DATABASE["user"],
    passwd=DATABASE["password"],
    host=DATABASE["host"],
    port=DATABASE["port"],
)


# pylint: disable=too-few-public-methods
class BaseModel(Model):
    """
    Base class for all database models, specifying the connection to the database.

    Attributes:
    -----------
    database : MySQLDatabase
        The database connection used by the derived models.
    """

    class Meta:
        """
        Meta:
        -----
        database : str
            The name of the table in the database ("change").
        """

        database = database


# pylint: disable=too-few-public-methods
class RoleModel(BaseModel):
    """
    Represents the user roles in the application.

    Attributes:
    -----------
    id : AutoField
        The primary key for the role.
    name : CharField
        The name of the role, with a maximum length of 100 characters.

    Meta:
    -----
    table_name : str
        The name of the table in the database ("role").
    """

    id = AutoField(primary_key=True)
    name = CharField(max_length=100)

    class Meta:
        """
        Meta:
        -----
        table_name : str
            The name of the table in the database ("change").
        """

        table_name = "role"


# pylint: disable=too-few-public-methods
class UserModel(BaseModel):
    """
    Represents a user in the application.

    Attributes:
    -----------
    id : AutoField
        The primary key for the user.
    email : CharField
        The user's email address, must be unique.
    password : CharField
        The user's password.
    role_id : ForeignKeyField
        A foreign key reference to the RoleModel, establishing the user's role.

    Meta:
    -----
    table_name : str
        The name of the table in the database ("user").
    """

    id = AutoField(primary_key=True)
    email = CharField(max_length=100, unique=True)
    password = CharField(max_length=100)
    role_id = ForeignKeyField(RoleModel, backref="users", on_delete="CASCADE")

    class Meta:
        """
        Meta:
        -----
        table_name : str
            The name of the table in the database ("change").
        """

        table_name = "user"


# pylint: disable=too-few-public-methods
class StatusModel(BaseModel):
    """
    Represents the statuses for tasks in the application.

    Attributes:
    -----------
    id : AutoField
        The primary key for the status.
    name : CharField
        The name of the status, with a maximum length of 100 characters.

    Meta:
    -----
    table_name : str
        The name of the table in the database ("status").
    """

    id = AutoField(primary_key=True)
    name = CharField(max_length=100)

    class Meta:
        """
        Meta:
        -----
        table_name : str
            The name of the table in the database ("change").
        """

        table_name = "status"


# pylint: disable=too-few-public-methods
class TaskModel(BaseModel):
    """
    Represents a task in the application.

    Attributes:
    -----------
    id : AutoField
        The primary key for the task.
    title : CharField
        The title of the task, with a maximum length of 255 characters.
    description : TextField
        A detailed description of the task.
    date_of_creation : DateField
        The date when the task was created.
    expiration_date : DateField
        The date when the task is set to expire.
    status_id : ForeignKeyField
        A foreign key reference to the StatusModel, indicating the task's status.
    user_id : ForeignKeyField
        A foreign key reference to the UserModel, indicating the user responsible for the task.
    is_favorite : BooleanField
        A flag indicating whether the task is marked as a favorite.

    Meta:
    -----
    table_name : str
        The name of the table in the database ("task").
    """

    id = AutoField(primary_key=True)
    title = CharField(max_length=255)
    description = TextField()
    date_of_creation = DateField()
    expiration_date = DateField()
    status_id = ForeignKeyField(StatusModel, backref="tasks", on_delete="CASCADE")
    user_id = ForeignKeyField(UserModel, backref="tasks", on_delete="CASCADE")
    is_favorite = BooleanField(default=False)

    class Meta:
        """
        Meta:
        -----
        table_name : str
            The name of the table in the database ("change").
        """

        table_name = "task"


# pylint: disable=too-few-public-methods
class ChangeModel(BaseModel):
    """
    Tracks changes made to tasks in the application.

    Attributes:
    -----------
    id : AutoField
        The primary key for the change record.
    task_id : ForeignKeyField
        A foreign key reference to the TaskModel, indicating which task was changed.
    timestamp : DateTimeField
        The date and time when the change occurred.
    field_changed : CharField
        The name of the field that was changed, with a maximum length of 255 characters.
    old_value : TextField
        The value of the field before the change.
    new_value : TextField
        The value of the field after the change.

    Meta:
    -----
    table_name : str
        The name of the table in the database ("change").
    """

    id = AutoField(primary_key=True)
    task_id = ForeignKeyField(TaskModel, backref="changes", on_delete="CASCADE")
    timestamp = DateTimeField()
    field_changed = CharField(max_length=255)
    old_value = TextField()
    new_value = TextField()

    class Meta:
        """
        Meta:
        -----
        table_name : str
            The name of the table in the database ("change").
        """

        table_name = "change"



File: To-do-List/FastAPI/app/config/__init__.py
===============================================



File: To-do-List/FastAPI/app/config/settings.py
===============================================
"""
This module configures the database connection settings for the application 
using environment variables loaded from a .env file.

The database configuration is determined based on the application environment 
(Development or Production). It uses the `dotenv` library to load environment 
variables and sets up a dictionary with database connection parameters.

The following environment variables are expected to be set in the .env file:
- MYSQL_DATABASE: Name of the MySQL database.
- MYSQL_USER: MySQL database user.
- MYSQL_PASSWORD: MySQL user password.
- MYSQL_HOST: Host where the MySQL database is located.
- MYSQL_PORT: Port number for the MySQL database (typically 3306).

The module sets the `DATABASE` dictionary with the necessary parameters for 
connecting to a MySQL database using the Peewee ORM.

Usage:
------
To use this module, ensure that the required environment variables are defined 
in a .env file located in the project root directory. The `load_dotenv()` 
function will automatically load these variables when this module is imported.
"""

import os
from dotenv import load_dotenv

load_dotenv()

ENV = os.getenv("ENV", "dev")

if ENV == "production":
    DATABASE = {
        "name": os.getenv("MYSQL_DATABASE"),
        "engine": "peewee.MySQLDatabase",
        "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",
        "user": os.getenv("MYSQL_USER"),
        "password": os.getenv("MYSQL_PASSWORD"),
        "host": os.getenv("MYSQL_HOST"),
        "port": int(os.getenv("MYSQL_PORT")),
    }

SECRET_KEY = os.getenv("SECRET_KEY")
ALGORITHM = "HS256"
ADMIN_ROLE_ID = 1
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES"))



File: To-do-List/FastAPI/app/services/role_service.py
=====================================================
"""
Service layer for Role operations.

This module contains the business logic for managing roles.
It interacts with the `RoleModel` from the database and uses
the `Role` Pydantic model for data validation.
"""

from typing import Optional, List
from peewee import DoesNotExist
from config.database import RoleModel # pylint: disable=import-error, disable=E0611
from models.role import RoleCreate, RoleRead, RoleUpdate # pylint: disable=import-error


class RoleService:
    """Service layer for Role operations"""

    @staticmethod
    def create_role(role: RoleCreate) -> RoleRead:
        """
        Create a new role.

        Args:
            role (RoleCreate): The role data for creation.

        Returns:
            RoleRead: The created role instance.
        """
        role_instance = RoleModel.create(**role.dict())
        return RoleRead.from_orm(role_instance)

    @staticmethod
    def get_role_by_id(role_id: int) -> Optional[RoleRead]:
        """
        Retrieve a role by ID.

        Args:
            role_id (int): The ID of the role to retrieve.

        Returns:
            Optional[RoleRead]: The role instance if found, else None.
        """
        try:
            role_instance = RoleModel.get_by_id(role_id)
            return RoleRead.from_orm(role_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def update_role(role_id: int, role: RoleUpdate) -> Optional[RoleRead]:
        """
        Update an existing role by ID.

        Args:
            role_id (int): The ID of the role to update.
            role (RoleUpdate): The new role data.

        Returns:
            Optional[RoleRead]: The updated role instance if successful, else None.
        """
        try:
            role_instance = RoleModel.get_by_id(role_id)
            if role.name:
                role_instance.name = role.name
            role_instance.save()
            return RoleRead.from_orm(role_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def delete_role(role_id: int) -> bool:
        """
        Delete a role by ID.

        Args:
            role_id (int): The ID of the role to delete.

        Returns:
            bool: True if the role was deleted, else False.
        """
        try:
            role_instance = RoleModel.get_by_id(role_id)
            role_instance.delete_instance()
            return True
        except DoesNotExist:
            return False

    @staticmethod
    def get_all_roles() -> List[RoleRead]:
        """
        Retrieve all roles.

        Returns:
            List[RoleRead]: A list of all role instances.
        """
        roles = RoleModel.select().execute()
        return [RoleRead.from_orm(role) for role in roles]



File: To-do-List/FastAPI/app/services/_init_.py
===============================================
"""
This module aggregates various service modules related to the application's core functionalities.

It includes services for managing roles, tasks, users, authentication, and status operations.

Attributes:
    RoleService: Service for managing roles.
    TaskService: Service for managing tasks.
    UserService: Service for managing users.
    create_access_token: Function to create access tokens for users.
    authenticate_user: Function to authenticate users.
    get_current_active_user: Function to retrieve the currently authenticated user.
    StatusService: Service for managing task statuses.
"""

from .role_service import RoleService # pylint: disable=import-error, relative-beyond-top-level
from .task_service import TaskService # pylint: disable=import-error, relative-beyond-top-level
from .user_service import UserService # pylint: disable=import-error, relative-beyond-top-level
from .auth_service import create_access_token, authenticate_user, get_current_active_user # pylint: disable=import-error, relative-beyond-top-level
from .status_service import StatusService # pylint: disable=import-error, relative-beyond-top-level

__all__ = [
    "RoleService",
    "TaskService",
    "UserService",
    "create_access_token",
    "authenticate_user",
    "get_current_active_user",
    "StatusService",
]



File: To-do-List/FastAPI/app/services/task_service.py
=====================================================
"""
Service layer for Task operations.

This module contains the business logic for managing tasks.
It interacts with the `TaskModel` from the database and uses
the `Task` Pydantic model for data validation.
"""

from datetime import datetime, date
from typing import Optional, List
from peewee import DoesNotExist
from fastapi import HTTPException, status

from config.database import (  # pylint: disable=import-error, disable=E0611
    TaskModel,  # pylint: disable=import-error
    StatusModel,  # pylint: disable=import-error
    UserModel,  # pylint: disable=import-error
)
from models.task import TaskCreate, TaskRead, TaskUpdate  # pylint: disable=import-error
from models.change import Change  # pylint: disable=import-error
from models.user import UserRead  # pylint: disable=import-error


class TaskService:
    """Service layer for Task operations"""

    @staticmethod
    def create_task(task: TaskCreate, current_user: UserRead) -> TaskRead:
        """
        Create a new task.

        Args:
            task (TaskCreate): The task data for creation.
            current_user (UserRead): The current authenticated user.

        Returns:
            TaskRead: The created task instance.

        Raises:
            ValueError: If the status or user does not exist.
            HTTPException: If the user does not have permission to assign tasks.
        """
        try:
            status_instance = StatusModel.get_by_id(task.status_id)
        except DoesNotExist as exc:
            raise ValueError(f"Status with id {task.status_id} not found") from exc

        # Asignar al usuario actual si no se especifica otro
        user_id = task.user_id if task.user_id else current_user.id

        if (
            task.user_id
            and task.user_id != current_user.id
            and current_user.role.name != "Administrator"
        ):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="You do not have permission to assign tasks to other users.",
            )

        try:
            user_instance = UserModel.get_by_id(user_id)
        except DoesNotExist as exc:
            raise ValueError(f"User with id {user_id} not found") from exc

        task_instance = TaskModel.create(
            title=task.title,
            description=task.description,
            date_of_creation=datetime.utcnow().date(),
            expiration_date=task.expiration_date,
            status=status_instance,
            user=user_instance,
            is_favorite=task.is_favorite or False,
        )
        return TaskRead.from_orm(task_instance)

    @staticmethod
    def get_task_by_id(task_id: int, current_user: UserRead) -> Optional[TaskRead]:
        """
        Retrieve a task by ID.

        Args:
            task_id (int): The ID of the task to retrieve.
            current_user (UserRead): The current authenticated user.

        Returns:
            Optional[TaskRead]: The task instance if found, else None.

        Raises:
            HTTPException: If the user does not have permission to view the task.
        """
        try:
            task_instance = TaskModel.get_by_id(task_id)
            if (
                task_instance.user.id != current_user.id
                and current_user.role.name != "Administrator"
            ):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="You do not have permission to view this task",
                )
            return TaskRead.from_orm(task_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def _get_task(task_id: int, current_user: UserRead) -> TaskModel:
        """
        Helper method to retrieve a task and verify user permissions.

        Args:
            task_id (int): The ID of the task to retrieve.
            current_user (UserRead): The current authenticated user.

        Returns:
            TaskModel: The retrieved task instance.

        Raises:
            HTTPException: If the task is not found or the user lacks permissions.
        """
        try:
            task_instance = TaskModel.get_by_id(task_id)
            if (
                task_instance.user.id != current_user.id
                and current_user.role.name != "Administrator"
            ):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="You do not have permission to view this task",
                )
            return TaskRead.from_orm(task_instance)
        except DoesNotExist as exc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Task not found",
            ) from exc

    @staticmethod
    def _update_status(task: TaskModel, status_id: Optional[int]):
        """
        Helper method to update the status of a task.

        Args:
            task (TaskModel): The task instance to update.
            status_id (Optional[int]): The new status ID.

        Raises:
            ValueError: If the status does not exist.
        """
        if status_id:
            try:
                status_instance = StatusModel.get_by_id(status_id)
                if task.status != status_instance:
                    Change.create(
                        task=task,
                        timestamp=datetime.utcnow(),
                        field_changed="status",
                        old_value=task.status.name,
                        new_value=status_instance.name,
                    )
                    task.status = status_instance
            except DoesNotExist as exc:
                raise ValueError(f"Status with id {status_id} not found") from exc

    @staticmethod
    def _update_user(task: TaskModel, user_id: Optional[int], current_user: UserRead):
        """
        Helper method to update the user assigned to a task.

        Args:
            task (TaskModel): The task instance to update.
            user_id (Optional[int]): The new user ID.
            current_user (UserRead): The current authenticated user.

        Raises:
            HTTPException: If the user lacks permissions to assign to another user.
            ValueError: If the user does not exist.
        """
        if user_id and user_id != task.user.id:
            if (
                user_id != current_user.id
                and current_user.role.name != "Administrator"
            ):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="You do not have permission to assign tasks to other users",
                )
            try:
                user_instance = UserModel.get_by_id(user_id)
                Change.create(
                    task=task,
                    timestamp=datetime.utcnow(),
                    field_changed="user",
                    old_value=str(task.user.id),
                    new_value=str(user_instance.id),
                )
                task.user = user_instance
            except DoesNotExist as exc:
                raise ValueError(f"User with id {user_id} not found") from exc

    @staticmethod
    def _update_field(task: TaskModel, field_name: str, new_value, changes: List):
        """
        Helper method to update a single field of a task and record the change.

        Args:
            task (TaskModel): The task instance to update.
            field_name (str): The name of the field to update.
            new_value: The new value for the field.
            changes (List): A list to record the changes made.
        """
        current_value = getattr(task, field_name)
        if new_value is not None and new_value != current_value:
            changes.append((field_name, current_value, new_value))
            setattr(task, field_name, new_value)

    @staticmethod
    def update_task(
        task_id: int, task_data: TaskUpdate, current_user: UserRead
    ) -> Optional[TaskRead]:
        """
        Update an existing task by ID.

        Args:
            task_id (int): The ID of the task to update.
            task_data (TaskUpdate): The new task data.
            current_user (UserRead): The current authenticated user.

        Returns:
            Optional[TaskRead]: The updated task instance if successful, else None.

        Raises:
            HTTPException: If the user does not have permission to update the task.
            ValueError: If the status or user does not exist.
        """
        task_instance = TaskService._get_task(task_id, current_user)

        # Actualizar el estado si se proporciona
        TaskService._update_status(task_instance, task_data.status_id)

        # Actualizar el usuario asignado si se proporciona
        TaskService._update_user(task_instance, task_data.user_id, current_user)

        # Actualizar otros campos y recopilar cambios
        changes = []
        TaskService._update_field(task_instance, "title", task_data.title, changes)
        TaskService._update_field(
            task_instance, "description", task_data.description, changes
        )
        TaskService._update_field(
            task_instance, "expiration_date", task_data.expiration_date, changes
        )
        if task_data.is_favorite is not None:
            TaskService._update_field(
                task_instance, "is_favorite", task_data.is_favorite, changes
            )

        if changes:
            task_instance.save()
            for field, old, new in changes:
                Change.create(
                    task=task_instance,
                    timestamp=datetime.utcnow(),
                    field_changed=field,
                    old_value=str(old),
                    new_value=str(new),
                )

        return TaskRead.from_orm(task_instance)

    @staticmethod
    def delete_task(task_id: int, current_user: UserRead) -> bool:
        """
        Delete a task by ID.

        Args:
            task_id (int): The ID of the task to delete.
            current_user (UserRead): The current authenticated user.

        Returns:
            bool: True if the task was deleted, else False.

        Raises:
            HTTPException: If the user does not have permission to delete the task.
        """
        try:
            task_instance = TaskModel.get_by_id(task_id)
            if (
                task_instance.user.id != current_user.id
                and current_user.role.name != "Administrator"
            ):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="You do not have permission to delete this task",
                )
            task_instance.delete_instance()
            return True
        except DoesNotExist:
            return False

    @staticmethod
    def get_all_tasks(
        current_user: UserRead,
        status_id: Optional[int] = None,
        expiration_date: Optional[date] = None,
    ) -> List[TaskRead]:
        """
        Retrieve all tasks with optional filtering by status and expiration date.

        Args:
            current_user (UserRead): The current authenticated user.
            status_id (Optional[int], optional): Filter by status ID.
            expiration_date (Optional[date], optional): Filter by expiration date.

        Returns:
            List[TaskRead]: A list of task instances.
        """
        query = TaskModel.select()

        if current_user.role.name != "Administrator":
            query = query.where(TaskModel.user.id == current_user.id)

        if status_id:
            query = query.where(TaskModel.status.id == status_id)

        if expiration_date:
            query = query.where(TaskModel.expiration_date == expiration_date)

        tasks = query.execute()
        return [TaskRead.from_orm(task) for task in tasks]

    @staticmethod
    def mark_as_favorite(task_id: int, current_user: UserRead) -> Optional[TaskRead]:
        """
        Mark a task as favorite.

        Args:
            task_id (int): The ID of the task to mark.
            current_user (UserRead): The current authenticated user.

        Returns:
            Optional[TaskRead]: The updated task instance if successful, else None.

        Raises:
            HTTPException: If the user does not have permission to mark the task.
        """
        try:
            task_instance = TaskModel.get_by_id(task_id)
            if (
                task_instance.user.id != current_user.id
                and current_user.role.name != "Administrator"
            ):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="You do not have permission to mark this task as a favorite",
                )
            task_instance.is_favorite = True
            task_instance.save()
            Change.create(
                task=task_instance,
                timestamp=datetime.utcnow(),
                field_changed="is_favorite",
                old_value=str(False),
                new_value=str(True),
            )
            return TaskRead.from_orm(task_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def unmark_as_favorite(task_id: int, current_user: UserRead) -> Optional[TaskRead]:
        """
        Unmark a task as favorite.

        Args:
            task_id (int): The ID of the task to unmark.
            current_user (UserRead): The current authenticated user.

        Returns:
            Optional[TaskRead]: The updated task instance if successful, else None.

        Raises:
            HTTPException: If the user does not have permission to unmark the task.
        """
        try:
            task_instance = TaskModel.get_by_id(task_id)
            if (
                task_instance.user.id != current_user.id
                and current_user.role.name != "Administrator"
            ):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="You do not have permission to unmark this task as a favorite.",
                )
            task_instance.is_favorite = False
            task_instance.save()
            Change.create(
                task=task_instance,
                timestamp=datetime.utcnow(),
                field_changed="is_favorite",
                old_value=str(True),
                new_value=str(False),
            )
            return TaskRead.from_orm(task_instance)
        except DoesNotExist:
            return None



File: To-do-List/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` from the database and uses
the `User` Pydantic model for data validation.
"""

from typing import Optional, List
from pydantic import EmailStr
from peewee import DoesNotExist
from config.database import UserModel, RoleModel # pylint: disable=import-error
from models.user import UserCreate, UserRead, UserUpdate # pylint: disable=import-error
from services.auth_service import get_password_hash # pylint: disable=import-error


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

    @staticmethod
    def create_user(user: UserCreate) -> UserRead:
        """
        Create a new user with hashed password.
        """
        try:
            role_instance = RoleModel.get_by_id(user.role_id)
            hashed_password = get_password_hash(user.password)
            user_instance = UserModel.create(
                email=user.email, password=hashed_password, role=role_instance
            )
            return UserRead.from_orm(user_instance)
        except DoesNotExist as exc:
            raise ValueError(f"Role with id {user.role_id} not found") from exc
        except Exception as e:
            raise ValueError(f"Error al crear el usuario: {str(e)}") from e

    @staticmethod
    def get_user_by_id(user_id: int) -> Optional[UserRead]:
        """
        Retrieve a user by ID.

        Args:
            user_id (int): The ID of the user to retrieve.

        Returns:
            Optional[UserRead]: The user instance if found, else None.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            return UserRead.from_orm(user_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def get_user_by_email(email: EmailStr) -> Optional[UserRead]:
        """
        Retrieve a user by email.

        Args:
            email (EmailStr): The email of the user to retrieve.

        Returns:
            Optional[UserRead]: The user instance if found, else None.
        """
        try:
            user_instance = UserModel.get(UserModel.email == email)
            return UserRead.from_orm(user_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def update_user(user_id: int, user: UserUpdate) -> Optional[UserRead]:
        """
        Update an existing user by ID.

        Args:
            user_id (int): The ID of the user to update.
            user (UserUpdate): The new user data.

        Returns:
            Optional[UserRead]: The updated user instance if successful, else None.

        Raises:
            ValueError: If the role with the given ID does not exist.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)

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

            user_instance.save()
            return UserRead.from_orm(user_instance)

        except DoesNotExist:
            return None

    @staticmethod
    def delete_user(user_id: int) -> bool:
        """
        Delete a user by ID.

        Args:
            user_id (int): The ID of the user to delete.

        Returns:
            bool: True if the user was deleted, else False.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            user_instance.delete_instance()
            return True
        except DoesNotExist:
            return False

    @staticmethod
    def get_all_users() -> List[UserRead]:
        """
        Retrieve all users.

        Returns:
            List[UserRead]: A list of all user instances.
        """
        users = UserModel.select().execute()
        return [UserRead.from_orm(user) for user in users]



File: To-do-List/FastAPI/app/services/auth_service.py
=====================================================
"""
Module for managing authentication and authorization in the application.

Provides functionality for creating access tokens and retrieving the current user based on JWT.
Uses FastAPI's OAuth2 security and the 'jose' library for handling JWTs.
"""

from datetime import datetime, timedelta
from typing import Optional

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt

from app.models.user import UserRead  # pylint: disable=import-error
from app.config.settings import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES  # pylint: disable=import-error
from app.config.database import UserModel  # pylint: disable=import-error
from app.services.user_service import UserService  # pylint: disable=import-error
from app.utils.security import verify_password  # pylint: disable=import-error

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")

def authenticate_user(email: str, password: str) -> Optional[UserRead]:
    """Autentica a un usuario por email y contraseña."""
    user = UserService.get_user_by_email(email)
    if not user:
        return None
    if not verify_password(password, user.password):
        return None
    return user

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

async def get_current_user(token: str = Depends(oauth2_scheme)) -> UserRead:
    """Obtiene el usuario actual basado en el token JWT."""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Credenciales inválidas",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id: str = payload.get("sub")
        if user_id is None:
            raise credentials_exception
    except JWTError as exc:
        raise credentials_exception from exc

    user = UserModel.get_or_none(UserModel.id == int(user_id))
    if user is None:
        raise credentials_exception
    return UserRead.from_orm(user)

async def get_current_active_user(current_user: UserRead = Depends(get_current_user)) -> UserRead:
    """Obtiene el usuario activo actual. Placeholder para chequeos adicionales."""
    return current_user



File: To-do-List/FastAPI/app/services/status_service.py
=======================================================
"""
This module contains the business logic for managing task statuses.
It interacts with the `StatusModel` from the database and uses Pydantic models for data validation.
"""
from typing import Optional, List
from ..models.status import (  # pylint: disable=import-error, relative-beyond-top-level
    StatusCreate,  # pylint: disable=import-error
    StatusRead,    # pylint: disable=import-error
    StatusUpdate,  # pylint: disable=import-error
)  # pylint: disable=import-error
from ..models.status import StatusModel  # pylint: disable=import-error, relative-beyond-top-level
from ..models.enums import TaskStatusEnum  # pylint: disable=import-error, relative-beyond-top-level
from peewee import DoesNotExist, IntegrityError  # pylint: disable=wrong-import-order

class StatusService:
    """Service for managing task statuses."""

    @staticmethod
    def create_status(status: StatusCreate) -> StatusRead:
        """Create a new status (if allowed)."""
        if status.name not in TaskStatusEnum.__members__.values():
            raise ValueError("Invalid status name.")
        try:
            status_instance = StatusModel.create(name=status.name)
            return StatusRead.from_orm(status_instance)
        except IntegrityError as exc:
            raise ValueError("The status already exists.") from exc  # Re-raise with context

    @staticmethod
    def get_status_by_id(status_id: int) -> Optional[StatusRead]:
        """Get a status by its ID."""
        try:
            status_instance = StatusModel.get_by_id(status_id)
            return StatusRead.from_orm(status_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def update_status(status_id: int, updated_status: StatusUpdate) -> Optional[StatusRead]:
        """Update an existing status."""
        try:
            status_instance = StatusModel.get_by_id(status_id)
            if updated_status.name:
                if updated_status.name not in TaskStatusEnum.__members__.values():
                    raise ValueError("Invalid status name.")
                status_instance.name = updated_status.name
            status_instance.save()
            return StatusRead.from_orm(status_instance)
        except DoesNotExist:
            return None
        except IntegrityError as exc:  # Capture the original exception
            raise ValueError("Error updating status: The name already exists.") from exc  # Re-raise with context

    @staticmethod
    def delete_status(status_id: int) -> bool:
        """Delete a status by its ID."""
        try:
            status_instance = StatusModel.get_by_id(status_id)
            # Prevent deletion of predefined statuses
            if status_instance.name in [status.value for status in TaskStatusEnum]:
                raise ValueError("Cannot delete a predefined status.")
            status_instance.delete_instance()
            return True
        except DoesNotExist:
            return False
        except ValueError as ve:
            raise ve

    @staticmethod
    def get_all_statuses() -> List[StatusRead]:
        """Get all statuses."""
        statuses = StatusModel.select().execute()
        return [StatusRead.from_orm(status) for status in statuses]



File: To-do-List/FastAPI/app/utils/__init__.py
==============================================



File: To-do-List/FastAPI/app/utils/security.py
==============================================
"""
Module for password hashing and verification.

Provides utility functions for hashing passwords and verifying them using Passlib's CryptContext.
"""

from passlib.context import CryptContext

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

def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    Verifies a plain password against its hash.

    Args:
        plain_password (str): The plain text password.
        hashed_password (str): The stored hashed password.

    Returns:
        bool: True if the password matches, False otherwise.
    """
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
    """
    Generates a hash for a password.

    Args:
        password (str): The plain text password.

    Returns:
        str: The hash of the password.
    """
    return pwd_context.hash(password)



File: To-do-List/FastAPI/app/models/status.py
=============================================
"""
This module defines Pydantic and ORM models for managing task statuses within the application.
These models validate and serialize task status data, providing interfaces for database
interactions and data validation in API operations.

Imports:
    - External:
        - peewee.Model: Base model class for ORM support.
        - peewee.CharField, AutoField, Check, SQL: Field and constraint definitions.
        - pydantic.BaseModel: Base class for Pydantic models.
        - pydantic.field_validator: Decorator for field validation in Pydantic models.
    - Internal:
        - TaskStatusEnum (from enums): Enum defining valid task statuses.
        - database: Database connection instance.
        - Optional (typing): Type hint for optional fields.

Classes:
    StatusModel (Model):
        ORM model for the 'status' table, defining attributes for each status and ensuring 
        each status name is unique and conforms to TaskStatusEnum values.

    StatusCreate (BaseModel):
        Pydantic model for creating a new status. Ensures the `name` field is a valid 
        TaskStatusEnum value.

    StatusUpdate (BaseModel):
        Pydantic model for updating an existing status. Validates the optional `name` field 
        if provided.

    StatusRead (BaseModel):
        Pydantic model for reading status data, including the status `id` and `name`, and 
        enables ORM mode for seamless data conversion.
"""

from typing import Optional
from pydantic import BaseModel, field_validator
from peewee import Model, CharField, AutoField, Check, SQL
from database import database  # Ensure the path is correct
from .enums import TaskStatusEnum  # pylint: disable=import-error, relative-beyond-top-level

class StatusModel(Model):
    """
    Model representing the 'status' table, with predefined task status values.
    """
    id = AutoField(primary_key=True)
    name = CharField(
        null=False,
        unique=True,
        constraints=[Check(SQL(f'"name" IN {tuple(status.value for status in TaskStatusEnum)}'))]
    )

    class Meta:
        """Metadata for table name and database connection."""
        table_name = "status"
        database = database
        doc = "Table defining the different statuses for tasks."


class StatusCreate(BaseModel):
    """
    Pydantic model for creating a new status record.
    """
    name: TaskStatusEnum

    @field_validator("name")
    @classmethod
    def name_must_be_valid(cls, v):
        """Ensures that the 'name' field is not empty."""
        if not v:
            raise ValueError("The status name cannot be empty.")
        return v


class StatusUpdate(BaseModel):
    """
    Pydantic model for updating an existing status record.
    """
    name: Optional[TaskStatusEnum] = None

    @field_validator("name")
    @classmethod
    def name_must_be_valid(cls, v):
        """Validates that the 'name' field is not empty if provided."""
        if v is not None and not v:
            raise ValueError("The status name cannot be empty.")
        return v


class StatusRead(BaseModel):
    """
    Pydantic model for reading status data.
    """
    id: int
    name: TaskStatusEnum

    class Config:
        """Enables ORM mode for automatic data conversion from ORM models."""
        orm_mode = True



File: To-do-List/FastAPI/app/models/enums.py
============================================
"""
Imports:
    Enum (from enum): Base class for creating enumerated constants in Python.

Classes:
    TaskStatusEnum (str, Enum): Enumeration representing the possible statuses of a task.

Enumerations:
    TO_DO (str): Indicates that the task is yet to be started.
    IN_PROGRESS (str): Indicates that the task is currently in progress.
    COMPLETED (str): Indicates that the task has been completed.
"""

from enum import Enum

class TaskStatusEnum(str, Enum):
    """
    Enumeration for task statuses in a task management system.

    Attributes:
        TO_DO (str): Status indicating that the task has not been started.
        IN_PROGRESS (str): Status indicating that the task is currently being worked on.
        COMPLETED (str): Status indicating that the task has been completed.

    Usage:
        This enumeration can be used to specify and check the status of a task 
        within task management applications, ensuring consistency in status values.

    Example:
        status = TaskStatusEnum.TO_DO
        if status == TaskStatusEnum.IN_PROGRESS:
            # Perform actions for tasks in progress
    """
    TO_DO = "TO_DO"
    IN_PROGRESS = "IN_PROGRESS"
    COMPLETED = "COMPLETED"



File: To-do-List/FastAPI/app/models/_init_.py
=============================================
"""
This module serves as an interface for importing and exposing 
the key models and enums used within the application.

It includes:
- Database models for managing various entities: Status, Task, User, Role, and Change.
- An enumeration for defining the possible statuses of tasks.

The exported members include:
- StatusModel: Model for managing task statuses.
- TaskModel: Model for managing tasks.
- UserModel: Model for managing users.
- RoleModel: Model for managing user roles.
- ChangeModel: Model for tracking changes.
- TaskStatusEnum: Enum for predefined task statuses.
"""

from .status import StatusModel # pylint: disable=import-error, relative-beyond-top-level
from .task import TaskModel # pylint: disable=import-error, relative-beyond-top-level
from .user import UserModel # pylint: disable=import-error, relative-beyond-top-level
from .role import RoleModel # pylint: disable=import-error, relative-beyond-top-level
from .change import ChangeModel # pylint: disable=import-error, relative-beyond-top-level
from .enums import TaskStatusEnum # pylint: disable=import-error, relative-beyond-top-level

__all__ = [
    "StatusModel",
    "TaskModel",
    "UserModel",
    "RoleModel",
    "ChangeModel",
    "TaskStatusEnum",
]



File: To-do-List/FastAPI/app/models/task.py
===========================================
"""
This module defines the Pydantic models for managing tasks in the application.
The models are used to validate and serialize data related to tasks.
"""

from datetime import date
from typing import Optional
from pydantic import BaseModel, constr, field_validator


class TaskCreate(BaseModel):
    """
    Model to create a new task.

    Attributes:
    -----------
    title : constr
        Task title, must be between 1 and 255 characters.
    description : str
        Detailed description of the task.
    expiration_date : date
        Task expiration date.
    status_id : int
        Status ID of the task.
    user_id : Optional[int]
        ID of the user assigned to the task. 
        If not provided, it is assigned to the current user.
    is_favorite : Optional[bool]
        Indicates if the task is a favorite. Defaults to False.
    """
    title: constr(min_length=1, max_length=255)  # type: ignore
    description: str
    expiration_date: date
    status_id: int
    user_id: Optional[int] = None
    is_favorite: Optional[bool] = False

    @field_validator("title")
    @classmethod
    def title_must_not_be_empty(cls, v: str) -> str:
        """
        Validates that the task title is not empty.

        Args:
            v (str): The task title.

        Returns:
            str: The task title if valid.

        Raises:
            ValueError: If the title is empty or contains only spaces.
        """
        if not v.strip():
            raise ValueError("The task title cannot be empty.")
        return v


class TaskUpdate(BaseModel):
    """
    Model to update an existing task.

    Attributes:
    -----------
    title : Optional[constr]
        The new task title, if provided.
    description : Optional[str]
        The new task description, if provided.
    expiration_date : Optional[date]
        The new task expiration date, if provided.
    status_id : Optional[int]
        The new status ID of the task, if provided.
    user_id : Optional[int]
        The new user ID assigned to the task, if provided.
    is_favorite : Optional[bool]
        Indicates if the task is a favorite, if provided.
    """
    title: Optional[constr(min_length=1, max_length=255)] = None  # type: ignore
    description: Optional[str] = None
    expiration_date: Optional[date] = None
    status_id: Optional[int] = None
    user_id: Optional[int] = None
    is_favorite: Optional[bool] = None

    @field_validator("title")
    @classmethod
    def title_must_not_be_empty(cls, v: Optional[str]) -> Optional[str]:
        """
        Validates that the new task title is not empty, if provided.

        Args:
            v (Optional[str]): The new task title.

        Returns:
            Optional[str]: The new task title if valid.

        Raises:
            ValueError: If the title is empty or contains only spaces.
        """
        if v is not None and not v.strip():
            raise ValueError("The task title cannot be empty.")
        return v


class TaskRead(BaseModel):
    """
    Model to read task information.

    Attributes:
    -----------
    id : int
        Unique task identifier.
    title : str
        Task title.
    description : str
        Detailed description of the task.
    date_of_creation : date
        Task creation date.
    expiration_date : date
        Task expiration date.
    status_id : int
        Task status ID.
    user_id : int
        ID of the user assigned to the task.
    is_favorite : bool
        Indicates if the task is a favorite.
    """
    id: int
    title: str
    description: str
    date_of_creation: date
    expiration_date: date
    status_name: str
    user_id: int
    is_favorite: bool

    class Config:
        """
        Model configuration to enable ORM compatibility.
        """
        orm_mode = True



File: To-do-List/FastAPI/app/models/token.py
============================================
"""
This module defines the Token Pydantic model for JWT authentication.
"""

from pydantic import BaseModel

class Token(BaseModel):
    """
    Model representing a JWT access token.
    
    Attributes:
        access_token (str): The JWT token.
        token_type (str): The type of token (e.g., 'bearer').
    """
    access_token: str
    token_type: str



File: To-do-List/FastAPI/app/models/role.py
===========================================
"""
role.py
"""

from typing import Optional  # Standard library import
from pydantic import BaseModel  # Third-party import


class RoleBase(BaseModel): # pylint: disable=too-few-public-methods
    """
    Base class for representing a role.

    Attributes:
        name (str): The name of the role.
    """

    name: str


class RoleCreate(RoleBase): # pylint: disable=too-few-public-methods
    """
    Class for creating a new role.

    Inherits from RoleBase without additional attributes.
    """


class RoleUpdate(BaseModel): # pylint: disable=too-few-public-methods
    """
    Class for updating an existing role.

    Optional attributes:
        name (Optional[str]): The new name for the role.
    """

    name: Optional[str] = None


class RoleRead(RoleBase): # pylint: disable=too-few-public-methods
    """
    Class for representing a role read from the database.

    Inherits from RoleBase and includes the role's ID.

    Attributes:
        id (int): The unique ID of the role.

    Config:
        orm_mode (bool): Enables compatibility with ORM objects.
    """

    id: int

    class Config: # pylint: disable=too-few-public-methods
        """
        Pydantic configuration settings.
        """

        orm_mode = True



File: To-do-List/FastAPI/app/models/user.py
===========================================
"""
This module defines the User Pydantic models for data validation and serialization.
"""

from typing import Optional  # Standard library import
from pydantic import BaseModel, EmailStr  # Third-party import
from .role import RoleRead  # pylint: disable=E0402


class UserCreate(BaseModel):
    """
    Model for creating a new user.

    Attributes:
        email (EmailStr): The user's email address.
        password (str): The user's password.
        role_id (int): The ID of the role assigned to the user.
    """
    email: EmailStr
    password: str
    role_id: int


class UserUpdate(BaseModel):
    """
    Model for updating an existing user.

    Attributes:
        email (Optional[EmailStr]): The new email address.
        password (Optional[str]): The new password.
        role_id (Optional[int]): The new role ID.
    """
    email: Optional[EmailStr] = None
    password: Optional[str] = None
    role_id: Optional[int] = None


class UserRead(BaseModel):  # pylint: disable=R0903
    """
    Model for reading user information.

    Attributes:
        id (int): The unique identifier of the user.
        email (EmailStr): The user's email address.
        role (RoleRead): The role assigned to the user.
    """
    id: int
    email: EmailStr
    role: RoleRead

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



File: To-do-List/FastAPI/app/models/change.py
=============================================
"""
This module defines the Change class, which represents a model for tracking changes 
made to tasks or other entities within an application.

The Change class captures details about what field was changed, the old value, 
the new value, and when the change occurred.

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

from datetime import datetime
from pydantic import BaseModel


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

    Attributes:
    -----------
    id : int
        Unique identifier for the change record.
    task_id : int
        Identifier of the task associated with this change.
    timestamp : datetime
        The date and time when the change was made.
    field_changed : str
        The name of the field that was changed (e.g., 'status', 'description').
    old_value : str
        The previous value of the field before the change.
    new_value : str
        The new value of the field after the change.
    """

    id: int
    task_id: int
    timestamp: datetime
    field_changed: str
    old_value: str
    new_value: str



File: To-do-List/FastAPI/app/dependencies.py
============================================
"""
This module provides dependencies to ensure that the 
current user has the appropriate role (administrator or normal user).

Dependencies:
- `get_current_active_user`: A service that retrieves the current authenticated user.

Functions:
- `get_admin_user`: Ensures the current user is an administrator.
- `get_normal_user`: Ensures the current user is a normal user.
"""
from fastapi import Depends, HTTPException, status
from models.user import UserRead # pylint: disable=import-error
from services.auth_service import get_current_active_user # pylint: disable=import-error


def get_admin_user(current_user: UserRead = Depends(get_current_active_user)) -> UserRead:
    """
    Dependency to ensure that the current user is an administrator.
    """
    if current_user.role.name != "Administrator":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not have administrator permissions",
        )
    return current_user

def get_normal_user(current_user: UserRead = Depends(get_current_active_user)) -> UserRead:
    """
    Dependency to ensure that the current user is a normal user.
    """
    if current_user.role.name != "Usuario normal":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not have normal user permissions",
        )
    return current_user



File: To-do-List/FastAPI/app/routes/_init_.py
=============================================
"""
This module aggregates the routers for different parts of the application.

It includes routing configurations for authentication, user management, role management,
task management, and status management.

Attributes:
    auth_router: Router for authentication-related routes.
    user_router: Router for user-related routes.
    role_router: Router for role-related routes.
    task_router: Router for task-related routes.
    status_router: Router for status-related routes.
"""

from .auth_routes import router as auth_router # pylint: disable=import-error, relative-beyond-top-level
from .user_routes import router as user_router # pylint: disable=import-error, relative-beyond-top-level
from .role_routes import router as role_router # pylint: disable=import-error, relative-beyond-top-level
from .task_routes import router as task_router # pylint: disable=import-error, relative-beyond-top-level
from .status_routes import router as status_router # pylint: disable=import-error, relative-beyond-top-level

__all__ = [
    "auth_router",
    "user_router",
    "role_router",
    "task_router",
    "status_router",
]



File: To-do-List/FastAPI/app/routes/auth_routes.py
==================================================
"""
Module that defines the authentication routes.

This module uses FastAPI to define the routes that allow
users to authenticate and receive JWT tokens.
"""

from datetime import timedelta
from fastapi import APIRouter, HTTPException, status
from models.user import UserCreate, UserRead  # pylint: disable=import-error
from services.user_service import UserService  # pylint: disable=import-error
from services.auth_service import create_access_token  # pylint: disable=import-error
from config.settings import ACCESS_TOKEN_EXPIRE_MINUTES  # pylint: disable=import-error

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

@router.post("/register", status_code=status.HTTP_201_CREATED)
def register_user(user: UserCreate):
    """
    Route to register a new user.

    Args:
        user (UserCreate): The user data for registration.

    Returns:
        dict: A dictionary with the user data and the JWT token.

    Raises:
        HTTPException: If the email is already registered or other validation errors occur.
    """
    try:
        created_user = UserService.create_user(user)
        access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        access_token = create_access_token(
            data={"sub": str(created_user.id)},
            expires_delta=access_token_expires
        )
        return {
            "user": created_user,
            "access_token": access_token,
            "token_type": "bearer"
        }
    except ValueError as ve:
        raise HTTPException(status_code=400, detail=str(ve)) from ve



File: To-do-List/FastAPI/app/routes/user_routes.py
==================================================
"""
Module that defines the routes for managing users.

This module uses FastAPI to define the routes that allow
creating, reading, updating, and deleting users through a REST API.

Available routes:

- POST /users/: Creates a new user. (Administrators only)
- GET /users/: Retrieves all users. (Administrators only)
- GET /users/{user_id}: Retrieves user information by ID. (Administrators or the user themselves)
- PUT /users/{user_id}: Updates user information. (Administrators or the user themselves)
- DELETE /users/{user_id}: Deletes a user. (Administrators only)

Each route uses the `UserService` to interact with the
business logic related to users and enforces access control based on user roles.
"""

from typing import List, Optional
from fastapi import APIRouter, HTTPException, Depends, status
from models.user import UserCreate, UserRead, UserUpdate
from services.user_service import UserService
from services.auth_service import get_current_active_user
from models.user import UserRead as CurrentUserRead

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

def admin_required(current_user: CurrentUserRead = Depends(get_current_active_user)):
    """
    Dependency to ensure the current user is an administrator.

    Args:
        current_user (UserRead): The current authenticated user.

    Raises:
        HTTPException: If the user is not an administrator.

    Returns:
        UserRead: The current administrator user.
    """
    if current_user.role.name != "Administrator":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="No tienes permisos de Administrator",
        )
    return current_user

@router.post("/", response_model=UserRead, dependencies=[Depends(admin_required)])
def create_user(user: UserCreate) -> UserRead:
    """
    Create a new user.

    Args:
        user (UserCreate): The user data for creation.

    Returns:
        UserRead: The created user instance.

    Raises:
        HTTPException: If role is not found or other validation errors occur.
    """
    try:
        created_user = UserService.create_user(user)
        return created_user
    except ValueError as ve:
        raise HTTPException(status_code=400, detail=str(ve))

@router.get("/", response_model=List[UserRead], dependencies=[Depends(admin_required)])
def get_all_users() -> List[UserRead]:
    """
    Retrieve all users.

    Returns:
        List[UserRead]: A list of all user instances.
    """
    return UserService.get_all_users()

@router.get("/{user_id}", response_model=UserRead)
def get_user(
    user_id: int,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> UserRead:
    """
    Retrieve user information by ID.

    Args:
        user_id (int): The ID of the user to retrieve.
        current_user (UserRead): The current authenticated user.

    Returns:
        UserRead: The user with the specified ID.

    Raises:
        HTTPException: If the user is not found or access is denied.
    """
    user = UserService.get_user_by_id(user_id)
    if user:
        if current_user.role.name == "Administrator" or current_user.id == user.id:
            return user
        else:
            raise HTTPException(status_code=403, detail="No tienes permiso para ver este usuario")
    raise HTTPException(status_code=404, detail="Usuario no encontrado")

@router.put("/{user_id}", response_model=UserRead)
def update_user(
    user_id: int,
    user: UserUpdate,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> UserRead:
    """
    Update user information.

    Args:
        user_id (int): The ID of the user to update.
        user (UserUpdate): The new user data.
        current_user (UserRead): The current authenticated user.

    Returns:
        UserRead: The updated user instance.

    Raises:
        HTTPException: If the user is not found or access is denied.
    """
    existing_user = UserService.get_user_by_id(user_id)
    if existing_user:
        if current_user.role.name == "Administrator" or current_user.id == user_id:
            try:
                updated_user = UserService.update_user(user_id, user)
                if updated_user:
                    return updated_user
                raise HTTPException(status_code=404, detail="Usuario no encontrado")
            except ValueError as ve:
                raise HTTPException(status_code=400, detail=str(ve))
        else:
            raise HTTPException from (status_code=403, detail="No tienes permiso para actualizar este usuario")
    raise HTTPException(status_code=404, detail="Usuario no encontrado")

@router.delete("/{user_id}", status_code=204, dependencies=[Depends(admin_required)])
def delete_user(user_id: int) -> None:
    """
    Delete a user by ID.

    Args:
        user_id (int): The ID of the user to delete.

    Raises:
        HTTPException: If the user is not found.
    """
    success = UserService.delete_user(user_id)
    if not success:
        raise HTTPException(status_code=404, detail="Usuario no encontrado")
    return



File: To-do-List/FastAPI/app/routes/role_routes.py
==================================================
"""
Module that defines the routes for managing roles.

This module uses FastAPI to define the routes that allow
creating, reading, updating, and deleting roles through a REST API.

Available routes:

- POST /roles/: Creates a new role. (Administrators only)
- GET /roles/: Retrieves all roles. (Administrators only)
- GET /roles/{role_id}: Retrieves role information by ID. (Administrators only)
- PUT /roles/{role_id}: Updates role information. (Administrators only)
- DELETE /roles/{role_id}: Deletes a role. (Administrators only)

Each route uses the `RoleService` to interact with the
business logic related to roles and enforces access control based on user roles.
"""

from typing import List
from fastapi import APIRouter, HTTPException, Depends, status
from models.role import RoleCreate, RoleRead, RoleUpdate # pylint: disable=import-error
from services.role_service import RoleService # pylint: disable=import-error
from services.auth_service import get_current_active_user # pylint: disable=import-error
from models.user import UserRead as CurrentUserRead # pylint: disable=import-error, disable=C0412

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

def admin_required(current_user: CurrentUserRead = Depends(get_current_active_user)):
    """
    Dependency to ensure the current user is an administrator.

    Args:
        current_user (UserRead): The current authenticated user.

    Raises:
        HTTPException: If the user is not an administrator.

    Returns:
        UserRead: The current administrator user.
    """
    if current_user.role.name != "Administrator":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="No tienes permisos de Administrator",
        )
    return current_user

@router.post("/", response_model=RoleRead, dependencies=[Depends(admin_required)])
def create_role(role: RoleCreate) -> RoleRead:
    """
    Create a new role.

    Args:
        role (RoleCreate): The role data for creation.

    Returns:
        RoleRead: The created role instance.
    """
    role = RoleService.create_role(role)
    return role

@router.get("/", response_model=List[RoleRead], dependencies=[Depends(admin_required)])
def get_all_roles() -> List[RoleRead]:
    """
    Retrieve all roles.

    Returns:
        List[RoleRead]: A list of all role instances.
    """
    return RoleService.get_all_roles()

@router.get("/{role_id}", response_model=RoleRead, dependencies=[Depends(admin_required)])
def get_role(role_id: int) -> RoleRead:
    """
    Retrieve role information by ID.

    Args:
        role_id (int): The ID of the role to retrieve.

    Returns:
        RoleRead: The role instance.

    Raises:
        HTTPException: If the role is not found.
    """
    role = RoleService.get_role_by_id(role_id)
    if role:
        return role
    raise HTTPException(status_code=404, detail="Rol no encontrado")

@router.put("/{role_id}", response_model=RoleRead, dependencies=[Depends(admin_required)])
def update_role(role_id: int, role: RoleUpdate) -> RoleRead:
    """
    Update role information.

    Args:
        role_id (int): The ID of the role to update.
        role (RoleUpdate): The new role data.

    Returns:
        RoleRead: The updated role instance.

    Raises:
        HTTPException: If the role is not found.
    """
    updated_role = RoleService.update_role(role_id, role)
    if updated_role:
        return updated_role
    raise HTTPException(status_code=404, detail="Rol no encontrado")

@router.delete("/{role_id}", status_code=204, dependencies=[Depends(admin_required)])
def delete_role(role_id: int) -> None:
    """
    Delete a role by ID.

    Args:
        role_id (int): The ID of the role to delete.

    Raises:
        HTTPException: If the role is not found.
    """
    success = RoleService.delete_role(role_id)
    if not success:
        raise HTTPException(status_code=404, detail="Rol no encontrado")
    return



File: To-do-List/FastAPI/app/routes/task_routes.py
==================================================
"""
Module that defines the routes for managing tasks.

This module uses FastAPI to define the routes that allow
creating, reading, updating, and deleting tasks through a REST API.

Available routes:

- POST /tasks/: Creates a new task. (Administrators and users)
- GET /tasks/: Retrieves all tasks with optional filters. (Administrators can see all; users see only their tasks)
- GET /tasks/{task_id}: Retrieves task information by ID. (Administrators or the task owner)
- PUT /tasks/{task_id}: Updates task information. (Administrators or the task owner)
- DELETE /tasks/{task_id}: Deletes a task. (Administrators or the task owner)
- POST /tasks/{task_id}/favorite: Marks a task as favorite. (Administrators or the task owner)
- DELETE /tasks/{task_id}/favorite: Unmarks a task as favorite. (Administrators or the task owner)

Each route uses the `TaskService` to interact with the
business logic related to tasks and enforces access control based on user roles.
"""

from typing import List, Optional
from datetime import date
from fastapi import APIRouter, HTTPException, Depends, status, Query

from models.task import TaskCreate, TaskRead, TaskUpdate # pylint: disable=import-error
from services.task_service import TaskService # pylint: disable=import-error
from services.auth_service import get_current_active_user # pylint: disable=import-error
from models.user import UserRead as CurrentUserRead # pylint: disable=import-error

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

@router.post("/", response_model=TaskRead)
def create_task(
    task: TaskCreate,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> TaskRead:
    """
    Create a new task.

    Args:
        task (TaskCreate): The task data for creation.
        current_user (UserRead): The current authenticated user.

    Returns:
        TaskRead: The created task instance.

    Raises:
        HTTPException: If status or user is not found, or if access is denied.
    """
    try:
        return TaskService.create_task(task, current_user)
    except ValueError as ve:
        raise HTTPException(status_code=400, detail=str(ve))

@router.get("/", response_model=List[TaskRead])
def get_all_tasks(
    status_id: Optional[int] = Query(None, description="Filter by status ID"),
    expiration_date: Optional[date] = Query(None, description="Filter by expiration date"),
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> List[TaskRead]:
    """
    Retrieve all tasks with optional filtering by status and expiration date.

    Args:
        status_id (Optional[int], optional): Filter by status ID.
        expiration_date (Optional[date], optional): Filter by expiration date.
        current_user (UserRead): The current authenticated user.

    Returns:
        List[TaskRead]: A list of task instances.
    """
    return TaskService.get_all_tasks(current_user, status_id, expiration_date)

@router.get("/{task_id}", response_model=TaskRead)
def get_task(
    task_id: int,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> TaskRead:
    """
    Retrieve task information by ID.

    Args:
        task_id (int): The ID of the task to retrieve.
        current_user (UserRead): The current authenticated user.

    Returns:
        TaskRead: The task instance.

    Raises:
        HTTPException: If the task is not found or access is denied.
    """
    task = TaskService.get_task_by_id(task_id, current_user)
    if task:
        return task
    raise HTTPException(status_code=404, detail="Tarea no encontrada")

@router.put("/{task_id}", response_model=TaskRead)
def update_task(
    task_id: int,
    task: TaskUpdate,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> TaskRead:
    """
    Update task information.

    Args:
        task_id (int): The ID of the task to update.
        task (TaskUpdate): The new task data.
        current_user (UserRead): The current authenticated user.

    Returns:
        TaskRead: The updated task instance.

    Raises:
        HTTPException: If the task is not found or access is denied.
    """
    try:
        updated_task = TaskService.update_task(task_id, task, current_user)
        if updated_task:
            return updated_task
        raise HTTPException(status_code=404, detail="Tarea no encontrada")
    except ValueError as ve:
        raise HTTPException(status_code=400, detail=str(ve))

@router.delete("/{task_id}", status_code=204)
def delete_task(
    task_id: int,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> None:
    """
    Delete a task by ID.

    Args:
        task_id (int): The ID of the task to delete.
        current_user (UserRead): The current authenticated user.

    Raises:
        HTTPException: If the task is not found or access is denied.
    """
    success = TaskService.delete_task(task_id, current_user)
    if not success:
        raise HTTPException(status_code=404, detail="Tarea no encontrada")
    return

@router.post("/{task_id}/favorite", response_model=TaskRead)
def mark_as_favorite(
    task_id: int,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> TaskRead:
    """
    Mark a task as favorite.

    Args:
        task_id (int): The ID of the task to mark.
        current_user (UserRead): The current authenticated user.

    Returns:
        TaskRead: The updated task instance.

    Raises:
        HTTPException: If the task is not found or access is denied.
    """
    task = TaskService.mark_as_favorite(task_id, current_user)
    if task:
        return task
    raise HTTPException(status_code=404, detail="Tarea no encontrada")

@router.delete("/{task_id}/favorite", response_model=TaskRead)
def unmark_as_favorite(
    task_id: int,
    current_user: CurrentUserRead = Depends(get_current_active_user)
) -> TaskRead:
    """
    Unmark a task as favorite.

    Args:
        task_id (int): The ID of the task to unmark.
        current_user (UserRead): The current authenticated user.

    Returns:
        TaskRead: The updated task instance.

    Raises:
        HTTPException: If the task is not found or access is denied.
    """
    task = TaskService.unmark_as_favorite(task_id, current_user)
    if task:
        return task
    raise HTTPException(status_code=404, detail="Tarea no encontrada")



File: To-do-List/FastAPI/app/routes/status_routes.py
====================================================
"""
This module defines the routes for managing task statuses.
Includes operations for creating, reading, updating, and deleting statuses.
"""

from typing import List
from fastapi import APIRouter, HTTPException, Depends, status
from ..models.status import (  # pylint: disable=import-error, relative-beyond-top-level
    StatusRead,  # pylint: disable=import-error
)  # pylint: disable=import-error
from ..services.status_service import (  # pylint: disable=import-error, relative-beyond-top-level
    StatusService,  # pylint: disable=import-error
)  # pylint: disable=import-error
from ..services.auth_service import (  # pylint: disable=import-error, relative-beyond-top-level
    get_current_active_user,  # pylint: disable=import-error
)  # pylint: disable=import-error
from ..models.user import (  # pylint: disable=import-error, relative-beyond-top-level
    UserRead,  # pylint: disable=import-error
)  # pylint: disable=import-error

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

def admin_required(current_user: UserRead = Depends(get_current_active_user)) -> UserRead:
    """Dependency to ensure that the current user is an administrator."""
    if current_user.role.name.lower() != "administrator":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not have administrator permissions.",
        )
    return current_user

@router.get(
    "/",
    response_model=List[StatusRead],
    dependencies=[Depends(admin_required)]
)
def get_all_statuses() -> List[StatusRead]:
    """Retrieve all statuses."""
    return StatusService.get_all_statuses()

@router.get(
    "/{status_id}",
    response_model=StatusRead,
    dependencies=[Depends(admin_required)]
)
def get_status(status_id: int) -> StatusRead:
    """Retrieve a status by its ID."""
    status_obj = StatusService.get_status_by_id(status_id)
    if status_obj:
        return status_obj
    raise HTTPException(status_code=404, detail="Status not found")

@router.post(
    "/",
    status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
    dependencies=[Depends(admin_required)]
)
def create_status():
    """Handle the creation of a status. This method is not allowed."""
    raise HTTPException(status_code=405, detail="Method not allowed")

@router.put(
    "/{status_id}",
    status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
    dependencies=[Depends(admin_required)]
)
def update_status(status_id: int):
    """Handle the update of a status. This method is not allowed."""
    raise HTTPException(status_code=405, detail="Method not allowed")

@router.delete(
    "/{status_id}",
    status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
    dependencies=[Depends(admin_required)]
)
def delete_status(status_id: int):
    """Handle the deletion of a status. This method is not allowed."""
    raise HTTPException(status_code=405, detail="Method not allowed")



File: To-do-List/FastAPI/app/init_statuses.py
=============================================
"""Insert predefined statuses into the database if they do not already exist."""
from .models.status import StatusModel  # pylint: disable=import-error
from .models.enums import TaskStatusEnum
from peewee import IntegrityError  # pylint: disable=wrong-import-order

def initialize_statuses():
    """Insert predefined statuses into the database if they do not already exist."""
    for status in TaskStatusEnum:
        try:
            StatusModel.get_or_create(name=status.value)
        except IntegrityError:
            pass



File: To-do-List/FastAPI/app/main.py
====================================
"""
Main module for the FastAPI application.
"""
from contextlib import asynccontextmanager # pylint: disable=C0412
from fastapi import FastAPI
from starlette.responses import RedirectResponse

from app.routes import (
    auth_router,
    user_router,
    role_router,
    task_router,
    status_router
)
from app.database import database
from app.init_statuses import initialize_statuses

@asynccontextmanager
async def lifespan(app: FastAPI): # pylint: disable=unused-argument
    """
    Manages the FastAPI application lifecycle.
    It connects to the database at startup and closes it at shutdown.
    It also initializes the predefined states.
    """
    # Conectar a la base de datos si está cerrada
    if database.is_closed():
        database.connect()
    # Inicializar estados predefinidos
    initialize_statuses()
    try:
        yield
    finally:
        # Cerrar la conexión a la base de datos al apagar
        if not database.is_closed():
            database.close()

app = FastAPI(lifespan=lifespan)

@app.get("/", include_in_schema=False)
async def root():
    """
    Redirects the root to the API documentation.
    """
    return RedirectResponse(url="/docs")

# Incluir todos los routers
app.include_router(auth_router)
app.include_router(user_router)
app.include_router(role_router)
app.include_router(task_router)
app.include_router(status_router)



File: To-do-List/MySQL/Dockerfile
=================================
FROM mysql:8.0

ENV MYSQL_ROOT_PASSWORD root
ENV MYSQL_DATABASE to-do-list-database
ENV MYSQL_USER user
ENV MYSQL_PASSWORD root

EXPOSE 3306



File: To-do-List/.github/workflows/deploy.yml
=============================================
name: Deploy

on:
  push:
    branches: [ main ]

jobs:
  Deploy:
    name: Deploy to EC2
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Build & Deploy
      env:
        PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        HOST_NAME: ${{ secrets.SSH_HOST }}
        USER_NAME: ${{ secrets.USER_NAME }}

      run: |
        # Guardamos la llave privada y le asignamos los permisos correctos
        echo "$PRIVATE_KEY" > private_key.pem && chmod 400 private_key.pem

        # Nos conectamos a la instancia EC2 usando SSH
        ssh -o StrictHostKeyChecking=no -i private_key.pem ${USER_NAME}@${HOST_NAME} '

        # Verificamos si el directorio es un repositorio Git
        if [ ! -d "/home/ubuntu/prod-to-do-list/.git" ]; then
          # Si no es un repositorio Git, clonamos el repositorio
          git clone https://github.com/STXVXN06/To-do-List.git /home/ubuntu/prod-to-do-list;
        fi

        # Comandos para desplegar el contenedor de Docker
        cd /home/ubuntu/prod-to-do-list &&

        # Actualizamos el repositorio
        git checkout main &&
        git fetch --all &&
        git reset --hard origin/main &&
        git pull origin main &&

        # Construimos y ejecutamos el contenedor de Docker
        docker compose down &&
        docker compose build &&
        docker compose up -d
        '


File: To-do-List/.github/workflows/pylint.yml
=============================================
name: Pylint Check

on:
  push:
    branches:
      - "**" # Se ejecuta en todas las ramas
  pull_request:
    branches:
      - "**" # Se ejecuta en pull requests desde todas las ramas

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.12"

      - name: Install dependencies from requirements.txt
        run: |
          python -m pip install --upgrade pip
          pip install -r ${{ github.workspace }}/FastAPI/requirements.txt

      - name: Run Pylint on the whole project
        run: |
          pylint ./**/*.py

      - name: Analysing the code with pylint
        run: |
          if [ ${{ steps.rate.outputs.rate }} -gt 9 ]; then
            pylint $(git ls-files '*.py')
          else
            echo "Rate is not greater than 9. Skipping pylint analysis."
          fi

lo que pasa, es que he hecho pylint a mi carpeta app y me sale esto:

(venv) johan@johan-VMware-Virtual-Platform:~/Documentos/To-do-List/FastAPI$ pylint app/
************* Module app.main
app/main.py:8:0: E0611: No name 'auth_router' in module 'app.routes' (no-name-in-module)
app/main.py:8:0: E0611: No name 'user_router' in module 'app.routes' (no-name-in-module)
app/main.py:8:0: E0611: No name 'role_router' in module 'app.routes' (no-name-in-module)
app/main.py:8:0: E0611: No name 'task_router' in module 'app.routes' (no-name-in-module)
app/main.py:8:0: E0611: No name 'status_router' in module 'app.routes' (no-name-in-module)
app/main.py:19:19: W0621: Redefining name 'app' from outer scope (line 37) (redefined-outer-name)

------------------------------------------------------------------
Your code has been rated at 7.61/10 (previous run: 8.99/10, -1.37)