mira, te voy a pasar una lista completa de tareas que se tuvieron que hacer en uyn proyecto, como tal te voy a dar primero las tareas y luego te paso el código de todo lo que tiene, y me harás un listado de cosas que faltan por hacer, me dirás en qué archivo se tendría que hacer el cambio y me darás una descripción al respecto, que se entienda para que otros desarrolladores puedan aplicar esos cambios, si todo está bien pues me lo dices y no se coloque en el listado que todo está bien

Primera tarea del trello:

Implement user registration and login
Create API routes for user registration (POST /register) and login (POST /login).

Store password hashes securely using a hashing algorithm like bcrypt.

On login, generate and return a JWT to the client.

Validate JWT tokens on all protected endpoints.

Segunda tarea:
Implement role-based access control (RBAC)
Define roles (e.g., “admin”, “user”) in the database and associate them with users.

Implement middleware to check a user's role and restrict access to specific routes (e.g., admin-only actions).

Example: Only admins can manage users, but normal users can only manage their own tasks.

Tercera tarea:

Implement task management (CRUD)
Create Task (POST /tasks):

Route for users to create a task. Task fields include title, description, due date, and status (to-do, in-progress, completed).

Only authenticated users can create tasks.

Read Tasks (GET /tasks):

Users can list their tasks, with optional filtering by status and due date.

Admins can list tasks for all users.

Update Task (PUT /tasks/
):

Route for users to update their own tasks. Admins can update any task.

Changes should include a log of edits (title, description, due date, status changes).

Delete Task (DELETE /tasks/
):

Users can delete their own tasks. Admins can delete any task.


Cuarta tarea:

Create API Endpoints for User Management (Admin only)
Details: Allow the admin to list all registered users, edit their information, or delete users via API endpoints.

Quinta tarea:

Implement Task Favorites Feature
Details:

Create a new API endpoint to allow users to mark tasks as favorites.

Update the task model to track favorite status (e.g., boolean flag).

Implement API logic to return only the user’s favorite tasks when requested.

Sexta tarea:

Implement Task History Feature
Details:

Add logic to automatically create history records each time a task is updated (title, due date, status changes).

Store the changes (what was updated and by whom) in the database.

Create an API endpoint to retrieve the history of a specific task, visible only to task owners and admin

ahora, mira el código del proyecto de todo lo que se tiene:

Folder Structure:

To-do-List/
├─ .github/
│   .github/
│   └─ workflows/
│       workflows/
│       ├─ deploy.yml
│       └─ pylint.yml
├─ .gitignore
├─ docker-compose.yml
├─ FastAPI/
│   FastAPI/
│   ├─ alembic.ini
│   ├─ app/
│   │   app/
│   │   ├─ alembic/
│   │   │   alembic/
│   │   │   ├─ env.py
│   │   │   ├─ README
│   │   │   ├─ script.py.mako
│   │   │   └─ versions/
│   │   │       versions/
│   │   │       ├─ 6655c8f0dad7_initial_migration.py
│   │   │       └─ d89dc7a9d248_initial_migration.py
│   │   ├─ alembic.ini
│   │   ├─ config/
│   │   │   config/
│   │   │   ├─ database.py
│   │   │   ├─ settings.py
│   │   │   └─ __init__.py
│   │   ├─ main.py
│   │   ├─ migrations.py
│   │   ├─ models/
│   │   │   models/
│   │   │   ├─ change.py
│   │   │   ├─ role.py
│   │   │   ├─ status.py
│   │   │   ├─ task.py
│   │   │   ├─ user.py
│   │   │   └─ __init__.py
│   │   ├─ routes/
│   │   │   routes/
│   │   │   ├─ auth_routes.py
│   │   │   ├─ task_routes.py
│   │   │   ├─ user_management_routes.py
│   │   │   └─ __init__.py
│   │   ├─ services/
│   │   │   services/
│   │   │   ├─ auth_service.py
│   │   │   ├─ task_service.py
│   │   │   ├─ user_service.py
│   │   │   └─ __init__.py
│   │   └─ utils/
│   │       utils/
│   │       ├─ dependencies.py
│   │       └─ __init__.py
│   ├─ Dockerfile
│   └─ requirements.txt
└─ MySQL/
    MySQL/
    └─ Dockerfile


File Contents:

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



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/alembic.ini
====================================
Contenido binario o no legible.


File: To-do-List/FastAPI/app/alembic/env.py
===========================================
"""
This module configures Alembic to handle database migrations.

It is responsible for establishing the connection to the database and defining
the behavior of migrations, both in 'offline' and 'online' modes.
"""
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from migrations import Base, DATABASE_URL  # pylint: disable=import-error

# This Alembic configuration object provides access to the
# values within the ini file in use.
config = context.config  # pylint: disable=no-member
config.set_main_option("sqlalchemy.url", DATABASE_URL)

# Interpret the config file for logging configuration.
# This line essentially configures the loggers.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

# Add your model's MetaData object here for 'autogenerate' support
target_metadata = Base.metadata

def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL and not with an Engine,
    although an Engine is also acceptable here. By skipping the creation
    of the Engine, we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to
    the script output.
    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(  # pylint: disable=no-member
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

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

def run_migrations_online() -> None:
    """Run migrations in 'online' mode.

    In this scenario, we need to create an Engine and associate a
    connection with the context.
    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

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

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

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



File: To-do-List/FastAPI/app/alembic/README
===========================================
Contenido binario o no legible.


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


File: To-do-List/FastAPI/app/alembic/versions/6655c8f0dad7_initial_migration.py
===============================================================================
"""initial migration

Revision ID: 6655c8f0dad7
Revises: 
Create Date: 2024-11-02 23:43:39.586714

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


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


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


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



File: To-do-List/FastAPI/app/alembic/versions/d89dc7a9d248_initial_migration.py
===============================================================================
"""initial migration

Revision ID: d89dc7a9d248
Revises: 6655c8f0dad7
Create Date: 2024-11-03 00:32:31.258419

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


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


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###



File: To-do-List/FastAPI/app/alembic.ini
========================================
Contenido binario o no legible.


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

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

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

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

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


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

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


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

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


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

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


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

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



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

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

import os
from dotenv import load_dotenv

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

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

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

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



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



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

from contextlib import asynccontextmanager
from starlette.responses import RedirectResponse
from routes import task_router, user_management_router, auth_routes

# Routers

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


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

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

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


app = FastAPI(lifespan=lifespan)

# On Startup
# On Shutdown


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


# Include routers for events and tickets
app.include_router(auth_routes.router)
app.include_router(task_router)
app.include_router(user_management_router)



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

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

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




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

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


class User(Base):# pylint: disable=too-few-public-methods
    """User table for storing user information and their associated roles."""
    __tablename__ = "user"
    id = Column(Integer, primary_key=True, index=True)
    email = Column(String(100), unique=True, nullable=False)
    password = Column(String(255), nullable=False)
    role_id = Column(Integer, ForeignKey('role.id'), nullable=True)

    role = relationship("Role", back_populates="users")

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

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

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

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

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

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

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



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

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


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

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

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



File: 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'),
        representing the permissions associated with it.
    """

    id: int
    name: str



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/task.py
===========================================
"""
This module defines the Task class, which represents a task model for handling 
task-related data within an application.

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

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

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


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

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

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

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



File: To-do-List/FastAPI/app/models/user.py
===========================================
"""
    Represents a user with its credentials and associated role.
"""
from typing import Optional
from pydantic import BaseModel, EmailStr
from .role import Role

class User(BaseModel):
    """
    Represents a user with their credentials and associated role.
    """
    id: int
    email: EmailStr
    hashed_password: str
    role_id: int
    is_active: bool
    role: Role

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

class UserCreate(BaseModel):
    """
    Model for creating a new user.
    """
    email: EmailStr
    password: str
    role_id: int

class UserUpdate(BaseModel):
    """
    Model for updating an existing user.
    """
    email: Optional[EmailStr] = None
    password: Optional[str] = None
    role_id: Optional[int] = None



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



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

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

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


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


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


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

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

    Returns:
        User: Registered user.

    Raises:
        HTTPException: If the email is already in use or there is an error during creation.
    """
    existing_user = UserService.get_user_by_email(user.email)
    if existing_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered",
        )
    hashed_password = AuthService.get_password_hash(user.password)
    try:
        new_user = UserService.create_user(
            email=user.email,
            password=hashed_password,
            role_id=user.role_id
        )
        return new_user
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e),
        ) from e


@router.post("/login", response_model=Token)
def login(user: UserCreate):
    """
    Endpoint to authenticate a user and provide a JWT token.

    Args:
        user (UserCreate): User credentials.

    Returns:
        Token: JWT token for authentication.

    Raises:
        HTTPException: If the credentials are invalid.
    """
    user_auth = AuthService.authenticate_user(user.email, user.password)
    if not user_auth:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = AuthService.create_access_token(
        data={"sub": user_auth.email},
        expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}



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

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

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

@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(
    title: str,
    description: Optional[str] = None,
    expiration_date: Optional[date] = None,
    status_id: int = 1,  # Default 'To Do'
    current_user: User = Depends(get_current_user)
) -> Task:
    """
    Create a new task.
    """
    task = TaskService.create_task(
        title=title,
        description=description,
        expiration_date=expiration_date,
        status_id=status_id,
        user_id=current_user.id
    )
    return task

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

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

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

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

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

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

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



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

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

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

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


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


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

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


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

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


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

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

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

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


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

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


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

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

    user.is_active = not user.is_active
    user.save()
    return {"message": f"User {user_id} is now {'active' if user.is_active else 'inactive'}"}



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

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



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

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

from fastapi import HTTPException, status
from jose import JWTError, jwt
from passlib.context import CryptContext
from config.settings import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
from services.user_service import UserService
from models.user import User

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


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

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

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

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

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

        Args:
            password (str): Plain text password.

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

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

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

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

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

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

        Returns:
            str: JWT token.
        """
        to_encode = data.copy()
        if expires_delta:
            expire = datetime.utcnow() + expires_delta
        else:
            expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        to_encode.update({"exp": expire})
        encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
        return encoded_jwt

    @staticmethod
    def get_current_user(token: str) -> User:
        """
        Retrieves the current user from the JWT token.

        Args:
            token (str): JWT token.

        Returns:
            User: Authenticated user.

        Raises:
            HTTPException: If the token is invalid or the user does not exist.
        """
        credentials_exception = HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )
        try:
            payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
            email: str = payload.get("sub")
            if email is None:
                raise credentials_exception
        except JWTError as ve:
            raise credentials_exception from ve
        user = UserService.get_user_by_email(email)
        if user is None:
            raise credentials_exception
        return user



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

from datetime import date, datetime
from typing import List, Optional
from models.task import Task
from models.change import Change


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

    @staticmethod
    def create_task(
        title: str,
        description: Optional[str],
        expiration_date: Optional[date],
        status_id: int,
        user_id: int,
    ) -> Task:
        """Creates a new task."""
        task = Task.create(
            title=title,
            description=description,
            expiration_date=expiration_date,
            status_id=status_id,
            user_id=user_id,
        )
        return task

    @staticmethod
    def get_task_by_id(task_id: int, user_id: int, is_admin: bool) -> Optional[Task]:
        """Retrieves a task by ID, checking user permissions."""
        task = Task.get_or_none(Task.id == task_id)
        if task and (task.user_id == user_id or is_admin):
            return task
        return None

    @staticmethod
    def update_task(
        task_id: int, user_id: int, is_admin: bool, **updates
    ) -> Optional[Task]:
        """Updates a task, logging changes, and checking user permissions."""
        task = Task.get_or_none(Task.id == task_id)
        if task and (task.user_id == user_id or is_admin):
            for key, value in updates.items():
                setattr(task, key, value)
            task.save()

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

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

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

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

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



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

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


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

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

    @staticmethod
    def get_user_by_id(user_id: int) -> Optional[User]:
        """
        Get a user by their ID.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            return User.model_validate(user_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def get_user_by_email(email: str) -> Optional[User]:
        """
        Get a user by their email.
        """
        try:
            user_instance = UserModel.get(UserModel.email == email)
            return User.model_validate(user_instance)
        except DoesNotExist:
            return None

    @staticmethod
    def update_user(
        user_id: int,
        email: Optional[str] = None,
        password: Optional[str] = None,
        role_id: Optional[int] = None,
    ) -> Optional[User]:
        """
        Update an existing user by their ID.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)

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

            user_instance.save()
            return User.model_validate(user_instance)

        except DoesNotExist:
            return None

    @staticmethod
    def delete_user(user_id: int) -> bool:
        """
        Delete a user by their ID.
        """
        try:
            user_instance = UserModel.get_by_id(user_id)
            user_instance.delete_instance()
            return True
        except DoesNotExist:
            return False

    @staticmethod
    def list_all_users() -> List[User]:
        """
        Get a list of all users.
        """
        users = UserModel.select()
        return [User.model_validate(user) for user in users]



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



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

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

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")


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


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


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



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



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
cffi==1.17.1
click==8.1.7
cryptography==43.0.1
dill==0.3.9
dnspython==2.7.0
ecdsa==0.19.0
email_validator==2.2.0
fastapi==0.115.0
greenlet==3.1.1
h11==0.14.0
idna==3.10
isort==5.13.2
Mako==1.3.5
MarkupSafe==3.0.1
mccabe==0.7.0
mypy-extensions==1.0.0
packaging==24.1
passlib==1.7.4
pathspec==0.12.1
peewee==3.17.6
platformdirs==4.3.6
pyasn1==0.6.1
pycparser==2.22
pydantic==2.9.2
pydantic_core==2.23.4
pylint==3.3.1
PyMySQL==1.1.1
python-dotenv==1.0.1
python-jose==3.3.0
rsa==4.9
six==1.16.0
sniffio==1.3.1
SQLAlchemy==2.0.35
starlette==0.38.6
tomlkit==0.13.2
typing_extensions==4.12.2
uvicorn==0.31.0



File: 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

Como seria entonces?