hola, mira lo que hay que hacer:

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.
 
la cosa es que yo ya tengo algo

Folder Structure:

To-do-List/
├─ .gitignore
├─ docker-compose.yml
├─ FastAPI/
│   FastAPI/
│   ├─ Dockerfile
│   ├─ requirements.txt
│   └─ app/
│       app/
│       ├─ database.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
│       ├─ models/
│       │   models/
│       │   ├─ status.py
│       │   ├─ _init_.py
│       │   ├─ task.py
│       │   ├─ role.py
│       │   ├─ user.py
│       │   └─ change.py
│       ├─ routes/
│       │   routes/
│       │   ├─ _init_.py
│       │   ├─ user_routes.py
│       │   ├─ role_routes.py
│       │   ├─ task_routes.py
│       │   └─ status_routes.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



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/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



File: To-do-List/FastAPI/app/services/role_service.py
=====================================================
"""
Module for managing roles in the application.

This module provides the RoleService class, which includes methods to create, 
read, update, and delete roles in the database.

Usage:
------
1. Create a role using `RoleService.create_role(role: Role)`.
2. Retrieve a role by its ID using `RoleService.get_role_by_id(role_id: int)`.
3. Update a role using `RoleService.update_role(role_id: int, updated_role: Role)`.
4. Delete a role using `RoleService.delete_role(role_id: int)`.
5. Retrieve all roles using `RoleService.get_all_roles()`.
"""

from typing import Optional, List
from models.role import Role
from config.database import RoleModel

class RoleService:
    """
    Service class to manage roles in the application.

    This class provides methods to create, read, update, and delete roles 
    in the database.
    """

    @staticmethod
    def create_role(role: Role) -> Role:
        """
        Create a new role in the database.

        Parameters:
        -----------
        role : Role
            The role object containing the necessary information to create 
            a new role.

        Returns:
        --------
        Role
            The created role object.
        """
        role_instance = RoleModel.create(**role.dict())
        return Role.from_orm(role_instance)

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

        Parameters:
        -----------
        role_id : int
            The ID of the role to retrieve.

        Returns:
        --------
        Optional[Role]
            The role object if found, or None if not found.
        """
        role_instance = RoleModel.get_or_none(RoleModel.id == role_id)
        if role_instance:
            return Role.from_orm(role_instance)
        return None

    @staticmethod
    def update_role(role_id: int, updated_role: Role) -> Optional[Role]:
        """
        Update an existing role in the database.

        Parameters:
        -----------
        role_id : int
            The ID of the role to update.
        updated_role : Role
            The role object containing updated information.

        Returns:
        --------
        Optional[Role]
            The updated role object if the role was found and updated, 
            or None if not found.
        """
        role_instance = RoleModel.get_or_none(RoleModel.id == role_id)
        if role_instance:
            for key, value in updated_role.dict(exclude_unset=True).items():
                setattr(role_instance, key, value)
            role_instance.save()
            return Role.from_orm(role_instance)
        return None

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

        Parameters:
        -----------
        role_id : int
            The ID of the role to delete.

        Returns:
        --------
        bool
            True if the role was successfully deleted, False if not found.
        """
        role_instance = RoleModel.get_or_none(RoleModel.id == role_id)
        if role_instance:
            role_instance.delete_instance()
            return True
        return False

    @staticmethod
    def get_all_roles() -> List[Role]:
        """
        Retrieve all roles from the database.

        Returns:
        --------
        List[Role]
            A list of all role objects.
        """
        roles = RoleModel.select().execute()
        return [Role.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 class to manage tasks in the application.
This class provides methods to create, read, update, and delete tasks.
"""

from datetime import datetime
from typing import Optional, List
from fastapi import Depends, HTTPException

# Local imports
from models.task import Task  # pylint: disable=import-error
from models.user import User  # pylint: disable=import-error
from models.change import Change  # pylint: disable=import-error
from config.database import (  # pylint: disable=import-error
    TaskModel,  # pylint: disable=import-error
    StatusModel,  # pylint: disable=import-error
    UserModel,  # pylint: disable=import-error
)  # pylint: disable=import-error
from services.auth_service import get_current_user  # pylint: disable=import-error


class TaskService:
    """
    Service class to manage tasks in the application.
    This class provides methods to create, read, update, and delete tasks.
    """

    @staticmethod
    def create_task(task: Task, current_user: User = Depends(get_current_user)) -> Task:
        """
        Create a new task in the database.
        """
        # Ensure the user and status exist
        if not UserModel.get_or_none(UserModel.id == task.user_id):
            raise ValueError("User not found")
        if not StatusModel.get_or_none(StatusModel.id == task.status_id):
            raise ValueError("Status not found")

        # Authorization: Check if the current user can create the task.
        if (
            task.user_id != current_user.id
            and current_user.role_name != "Administrator"
        ):
            raise HTTPException(
                status_code=403, detail="You do not have permission to create this task"
            )

        task_instance = TaskModel.create(**task.dict())
        return Task.from_orm(task_instance)

    @staticmethod
    def get_task_by_id(
        task_id: int, current_user: User = Depends(get_current_user)
    ) -> Optional[Task]:
        """
        Retrieve a task by its ID.
        """
        task_instance = TaskModel.get_or_none(TaskModel.id == task_id)
        if task_instance:
            # Check if the user has permission to view the task
            if (
                task_instance.user_id != current_user.id
                and current_user.role_name != "Administrator"
            ):
                raise HTTPException(
                    status_code=403,
                    detail="You do not have permission to view this task",
                )
            return Task.from_orm(task_instance)
        return None

    @staticmethod
    def update_task(
        task_id: int, updated_task: Task, current_user: User = Depends(get_current_user)
    ) -> Optional[Task]:
        """
        Update an existing task in the database.
        """
        task_instance = TaskModel.get_or_none(TaskModel.id == task_id)
        if task_instance:
            # Authorization: Check if the current user can update the task.
            if (
                task_instance.user_id != current_user.id
                and current_user.role_name != "Administrator"
            ):
                raise HTTPException(
                    status_code=403,
                    detail="You do not have permission to update this task",
                )

            # Recorrer los campos de la tarea y registrar los cambios
            for key, value in updated_task.dict(exclude_unset=True).items():
                old_value = getattr(task_instance, key, None)
                if old_value != value:  # Only record changes if there are differences
                    setattr(task_instance, key, value)
                    # Create a changelog
                    Change.create(
                        task_id=task_id,
                        timestamp=datetime.now(),
                        field_changed=key,
                        old_value=str(old_value),
                        new_value=str(value),
                    )

            task_instance.save()
            return Task.from_orm(task_instance)
        return None

    @staticmethod
    def delete_task(
        task_id: int, current_user: User = Depends(get_current_user)
    ) -> bool:
        """
        Delete a task by its ID.
        """
        task_instance = TaskModel.get_or_none(TaskModel.id == task_id)
        if task_instance:
            # Check if the current user can delete the task
            if (
                task_instance.user_id != current_user.id
                and current_user.role_name != "Administrator"
            ):
                raise HTTPException(
                    status_code=403,
                    detail="You do not have permission to delete this task",
                )

            task_instance.delete_instance()
            return True
        return False

    @staticmethod
    def get_all_tasks(current_user: User = Depends(get_current_user)) -> List[Task]:
        """
        Retrieve all tasks from the database.
        """
        if current_user.role_name == "Administrator":
            tasks = TaskModel.select().execute()
        else:
            tasks = (
                TaskModel.select().where(TaskModel.user_id == current_user.id).execute()
            )

        return [Task.from_orm(task) for task in tasks]



File: To-do-List/FastAPI/app/services/user_service.py
=====================================================
"""
Module for managing users in the application.

This module provides the UserService class, which includes methods to create, 
read, update, and delete users in the database. It validates the existence of 
roles before creating or updating users.

Usage:
------
1. Create a user using `UserService.create_user(user: User)`.
2. Retrieve a user by their ID using `UserService.get_user_by_id(user_id: int)`.
3. Update a user using `UserService.update_user(user_id: int, updated_user: User)`.
4. Delete a user using `UserService.delete_user(user_id: int)`.
5. Retrieve all users using `UserService.get_all_users()`.
"""

from typing import Optional, List
from models.user import User
from config.database import UserModel, RoleModel

class UserService:
    """
    Service class to manage users in the application.

    This class provides methods to create, read, update, and delete users, 
    as well as to retrieve all users from the database. It validates the 
    existence of roles before creating or updating users.
    """

    @staticmethod
    def create_user(user: User) -> User:
        """
        Create a new user in the database.

        Parameters:
        -----------
        user : User
            The user object containing the necessary information to create 
            a new user.

        Returns:
        --------
        User
            The created user object.

        Raises:
        -------
        ValueError
            If the role does not exist.
        """
        if not RoleModel.get_or_none(RoleModel.id == user.role_id):
            raise ValueError("Rol no encontrado")

        user_instance = UserModel.create(**user.dict())
        return User.from_orm(user_instance)

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

        Parameters:
        -----------
        user_id : int
            The ID of the user to retrieve.

        Returns:
        --------
        Optional[User]
            The user object if found, or None if not found.
        """
        user_instance = UserModel.get_or_none(UserModel.id == user_id)
        if user_instance:
            return User.from_orm(user_instance)
        return None

    @staticmethod
    def update_user(user_id: int, updated_user: User) -> Optional[User]:
        """
        Update an existing user in the database.

        Parameters:
        -----------
        user_id : int
            The ID of the user to update.
        updated_user : User
            The user object containing updated information.

        Returns:
        --------
        Optional[User]
            The updated user object if the user was found and updated, 
            or None if not found.
        """
        user_instance = UserModel.get_or_none(UserModel.id == user_id)
        if user_instance:
            for key, value in updated_user.dict(exclude_unset=True).items():
                setattr(user_instance, key, value)
            user_instance.save()
            return User.from_orm(user_instance)
        return None

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

        Parameters:
        -----------
        user_id : int
            The ID of the user to delete.

        Returns:
        --------
        bool
            True if the user was successfully deleted, False if not found.
        """
        user_instance = UserModel.get_or_none(UserModel.id == user_id)
        if user_instance:
            user_instance.delete_instance()
            return True
        return False

    @staticmethod
    def get_all_users() -> List[User]:
        """
        Retrieve all users from the database.

        Returns:
        --------
        List[User]
            A list of all user objects.
        """
        users = UserModel.select().execute()
        return [User.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.

This module provides functionality for creating access 
tokens and retrieving the current user based on JWT 
(JSON Web Tokens). It 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 models.user import User
from config.settings import SECRET_KEY, ALGORITHM
from config.database import UserModel

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """
    Create a JSON Web Token (JWT) access token.

    Parameters:
    -----------
    data : dict
        The data to encode in the JWT, typically including user identification.
    expires_delta : Optional[timedelta]
        An optional expiration time for the token; if not provided, the token
        will expire in 15 minutes.

    Returns:
    --------
    str
        The encoded JWT as a string.
    """
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    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)):
    """
    Retrieve the current user based on the provided JWT token.

    Parameters:
    -----------
    token : str
        The JWT token obtained from the request.

    Raises:
    -------
    HTTPException
        If the token is invalid or if the user cannot be found.

    Returns:
    --------
    User
        The User object corresponding to the authenticated user.
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        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 User.from_orm(user)


async def get_current_active_user(current_user: User = Depends(get_current_user)):
    """
    Retrieve the current active user, potentially adding additional checks
    for account status.

    Parameters:
    -----------
    current_user : User
        The currently authenticated user.

    Returns:
    --------
    User
        The active User object.
    """
    return current_user



File: To-do-List/FastAPI/app/services/status_service.py
=======================================================
"""
Module for managing statuses in the application.

This module provides the StatusService class, which includes methods to create, 
read, update, and delete statuses in the database.

Usage:
------
1. Create a status using `StatusService.create_status(status: Status)`.
2. Retrieve a status by its ID using `StatusService.get_status_by_id(status_id: int)`.
3. Update a status using `StatusService.update_status(status_id: int, updated_status: Status)`.
4. Delete a status using `StatusService.delete_status(status_id: int)`.
5. Retrieve all statuses using `StatusService.get_all_statuses()`.
"""

from typing import Optional, List
from models.status import Status # pylint: disable=import-error
from config.database import StatusModel # pylint: disable=import-error

class StatusService:
    """
    Service class to manage statuses in the application.

    This class provides methods to create, read, update, and delete statuses 
    in the database.
    """

    @staticmethod
    def create_status(status: Status) -> Status:
        """
        Create a new status in the database.

        Parameters:
        -----------
        status : Status
            The status object containing the necessary information to create 
            a new status.

        Returns:
        --------
        Status
            The created status object.
        """
        status_instance = StatusModel.create(**status.dict())
        return Status.from_orm(status_instance)

    @staticmethod
    def get_status_by_id(status_id: int) -> Optional[Status]:
        """
        Retrieve a status by its ID.

        Parameters:
        -----------
        status_id : int
            The ID of the status to retrieve.

        Returns:
        --------
        Optional[Status]
            The status object if found, or None if not found.
        """
        status_instance = StatusModel.get_or_none(StatusModel.id == status_id)
        if status_instance:
            return Status.from_orm(status_instance)
        return None

    @staticmethod
    def update_status(status_id: int, updated_status: Status) -> Optional[Status]:
        """
        Update an existing status in the database.

        Parameters:
        -----------
        status_id : int
            The ID of the status to update.
        updated_status : Status
            The status object containing updated information.

        Returns:
        --------
        Optional[Status]
            The updated status object if the status was found and updated, 
            or None if not found.
        """
        status_instance = StatusModel.get_or_none(StatusModel.id == status_id)
        if status_instance:
            for key, value in updated_status.dict(exclude_unset=True).items():
                setattr(status_instance, key, value)
            status_instance.save()
            return Status.from_orm(status_instance)
        return None

    @staticmethod
    def delete_status(status_id: int) -> bool:
        """
        Delete a status by its ID.

        Parameters:
        -----------
        status_id : int
            The ID of the status to delete.

        Returns:
        --------
        bool
            True if the status was successfully deleted, False if not found.
        """
        status_instance = StatusModel.get_or_none(StatusModel.id == status_id)
        if status_instance:
            status_instance.delete_instance()
            return True
        return False

    @staticmethod
    def get_all_statuses() -> List[Status]:
        """
        Retrieve all statuses from the database.

        Returns:
        --------
        List[Status]
            A list of all status objects.
        """
        statuses = StatusModel.select().execute()
        return [Status.from_orm(status) for status in statuses]



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

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

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

from pydantic import BaseModel


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

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

    id: int
    name: str



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



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

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

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

from datetime import date
from pydantic import BaseModel


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

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

    id: int
    title: str
    description: str
    date_of_creation: date
    expiration_date: date
    status_id: int
    user_id: int
    is_favorite: bool



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

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

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

from pydantic import BaseModel


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

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

    id: int
    name: str



File: To-do-List/FastAPI/app/models/user.py
===========================================
"""
This module defines the User class, which represents a user model for managing 
user-related data and authentication within an application.

The User class includes attributes for user credentials, an associated role 
(which is a reference to the Role model), and other relevant user information.

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

from pydantic import BaseModel
from .role import Role  # pylint: disable=relative-beyond-top-level


class User(BaseModel):
    """
    Represents a user with their credentials and associated role.

    Attributes:
    -----------
    id : int
        Unique identifier for the user.
    email : str
        The user's email address, used as their login.
    password : str
        The user's hashed password for authentication.
    role_id : int
        Identifier for the role associated with the user.
    role : Role
        The role object, representing the user's permissions and access levels.
    """

    id: int
    email: str
    password: str
    role_id: int
    role: Role



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/routes/_init_.py
=============================================



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



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



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



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



File: To-do-List/FastAPI/app/main.py
====================================
"""
Main module for the FastAPI application.
"""

from contextlib import asynccontextmanager
from fastapi import FastAPI
from starlette.responses import RedirectResponse

# Routers


from database import database as connection


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

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

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


app = FastAPI(lifespan=lifespan)

# On Startup
# On Shutdown


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


# Include routers for events and tickets



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


la cosa es que como tal necesito que todo eso de los metodos, de los servicios tenga lo de jwt, pero necesito que me ayudes a completar los servicios, ayudame en estos momentos solamente con los servicios, a ver si están bien, si faltan metodos, si hay que cambiar algo