como tal tengo esto:

Folder Structure:

To-do-List/
├─ .gitignore
├─ docker-compose.yml
├─ FastAPI/
│   FastAPI/
│   ├─ Dockerfile
│   ├─ requirements.txt
│   └─ app/
│       app/
│       ├─ __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/__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.
"""

import os
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()

# Configuración de la base de datos
DATABASE = {
    "name": os.getenv("MYSQL_DATABASE"),
    "user": os.getenv("MYSQL_USER"),
    "password": os.getenv("MYSQL_PASSWORD"),
    "host": os.getenv("MYSQL_HOST"),
    "port": int(os.getenv("MYSQL_PORT")),
}

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.
    """
    class 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.
    """
    id = AutoField(primary_key=True)
    name = CharField(max_length=100)

    class 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.
    """
    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:
        """
        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.
    """
    id = AutoField(primary_key=True)
    name = CharField(max_length=100)

    class 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.
    """

    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:
        """
        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.
    """
    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:
        """
        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
=================================================



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 app.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 ( # pylint: disable=import-error
    SECRET_KEY, # pylint: disable=import-error
    ALGORITHM, # pylint: disable=import-error
    ACCESS_TOKEN_EXPIRE_MINUTES, # pylint: disable=import-error
)  # 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 app.models.status import StatusCreate, StatusRead, StatusUpdate
from app.models.status import (
    StatusModel
)  # pylint: disable=import-error
from app.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

    @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

    @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 CharField, AutoField, Check, SQL
from app.config.database import database  # pylint: disable=import-error
from .enums import ( # pylint: disable=import-error
    TaskStatusEnum, # pylint: disable=import-error
)  # pylint: disable=import-error, relative-beyond-top-level


# pylint: disable=too-few-public-methods
class StatusModel(BaseModel):
    """
    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:
        # pylint: disable=too-few-public-methods
        """Metadata for table name and database connection."""

        table_name = "status"
        database = database
        doc = "Table defining the different statuses for tasks."


# pylint: disable=too-few-public-methods
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


# pylint: disable=too-few-public-methods
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


# pylint: disable=too-few-public-methods
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.
    """

    TO_DO = "TO_DO"
    IN_PROGRESS = "IN_PROGRESS"
    COMPLETED = "COMPLETED"



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



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.
    """

    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.
        """
        if not v.strip():
            raise ValueError("The task title cannot be empty.")
        return v


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

    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


# pylint: disable=too-few-public-methods
class TaskRead(BaseModel):
    """
    Model to read task information.
    """

    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.
    """

    name: str


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


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

    name: Optional[str] = None


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

    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.
    """

    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 ( # pylint: disable=import-error
    get_current_active_user, # pylint: disable=import-error
)  # 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
===============================================
"""
Module for API route management.

This module imports routers for handling authentication, user management, role management,
task management, and status management. It aggregates all the routers for easy inclusion
in the main FastAPI application.
"""

from app.routes.auth_routes import auth_router
from app.routes.user_routes import user_router
from app.routes.role_routes import role_router
from app.routes.task_routes import task_router
from app.routes.status_routes import status_router

__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
from fastapi import APIRouter, HTTPException, Depends, status
from models.user import UserRead as CurrentUserRead  # pylint: disable=import-error
from models.user import UserCreate, UserUpdate, UserRead  # pylint: disable=import-error
from services.user_service import UserService  # pylint: disable=import-error
from services.auth_service import (
    get_current_active_user,
)  # pylint: disable=import-error

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="You do not have Administrator permissions",
        )
    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)) from 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="You do not have permission to view this user"
            )
    raise HTTPException(status_code=404, detail="User not found")


@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="User not found")
            except ValueError as ve:
                raise HTTPException(status_code=400, detail=str(ve)) from ve
        else:
            raise HTTPException(
                status_code=403, detail="You do not have permission to update this user"
            )
    raise HTTPException(status_code=404, detail="User not found")


@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="User not found")
    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 ( # pylint: disable=import-error
    get_current_active_user, # pylint: disable=import-error
)  # pylint: disable=import-error
from models.user import ( # pylint: disable=import-error
    UserRead as CurrentUserRead, # pylint: disable=import-error
)  # 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, 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 ( # pylint: disable=import-error
    get_current_active_user, # pylint: disable=import-error
)  # pylint: disable=import-error
from models.user import UserRead as CurrentUserRead  # pylint: disable=import-error, disable=C0412

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)) from 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)) from 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 app.models.status import (  # pylint: disable=import-error, relative-beyond-top-level
    StatusRead,  # pylint: disable=import-error
)  # pylint: disable=import-error
from app.services.status_service import (  # pylint: disable=import-error, relative-beyond-top-level
    StatusService,  # pylint: disable=import-error
)  # pylint: disable=import-error
from app.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 app.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.config.database import database # pylint: disable=import-error
from app.init_statuses import initialize_statuses


@asynccontextmanager
async def lifespan(fastapi_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.
    """
    # Connects to the database if it is closed
    if database.is_closed():
        database.connect()
    # Initializes predefined states
    initialize_statuses()
    try:
        yield
    finally:
        # Closes database connection at shutdown
        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


pero necesito que en la database este los metodos que tiene los modelos, necesito que en los modelos solo esté los atributos de las cosas, me hago entender? y que ya los metodos de routas y servicios cambian porque ya los metodos que estaban en los modelos, ya estarán en el database