Folder Structure
Folder Structure
FastAPI/
├─ requirements.txt
├─ Dockerfile
├─ app/
│ app/
│ ├─ models/
│ │ models/
│ │ ├─ role.py
│ │ ├─ task.py
│ │ ├─ status.py
│ │ ├─ user.py
│ │ ├─ __pycache__/
│ │ │ __pycache__/
│ │ │ ├─ change.cpython-312.pyc
│ │ │ ├─ task.cpython-312.pyc
│ │ │ ├─ __init__.cpython-312.pyc
│ │ │ ├─ role.cpython-312.pyc
│ │ │ └─ user.cpython-312.pyc
│ │ ├─ __init__.py
│ │ └─ change.py
│ ├─ config/
│ │ config/
│ │ ├─ __pycache__/
│ │ │ __pycache__/
│ │ │ ├─ database.cpython-312.pyc
│ │ │ ├─ __init__.cpython-312.pyc
│ │ │ └─ settings.cpython-312.pyc
│ │ ├─ database.py
│ │ ├─ __init__.py
│ │ └─ settings.py
│ ├─ __pycache__/
│ │ __pycache__/
│ │ ├─ database.cpython-312.pyc
│ │ ├─ main.cpython-312.pyc
│ │ └─ migrations.cpython-312.pyc
│ ├─ migrations.py
│ ├─ alembic/
│ │ alembic/
│ │ ├─ versions/
│ │ │ versions/
│ │ │ ├─ __pycache__/
│ │ │ │ __pycache__/
│ │ │ │ ├─ 905731c6a76a_first_migrations.cpython-312.pyc
│ │ │ │ ├─ 6655c8f0dad7_initial_migration.cpython-312.pyc
│ │ │ │ └─ d89dc7a9d248_initial_migration.cpython-312.pyc
│ │ │ └─ 905731c6a76a_first_migrations.py
│ │ ├─ env.py
│ │ ├─ script.py.mako
│ │ ├─ __pycache__/
│ │ │ __pycache__/
│ │ │ └─ env.cpython-312.pyc
│ │ └─ README
│ ├─ services/
│ │ services/
│ │ ├─ __pycache__/
│ │ │ __pycache__/
│ │ │ ├─ user_service.cpython-312.pyc
│ │ │ ├─ __init__.cpython-312.pyc
│ │ │ ├─ auth_service.cpython-312.pyc
│ │ │ └─ task_service.cpython-312.pyc
│ │ ├─ auth_service.py
│ │ ├─ __init__.py
│ │ ├─ user_service.py
│ │ └─ task_service.py
│ ├─ main.py
│ ├─ utils/
│ │ utils/
│ │ ├─ __pycache__/
│ │ │ __pycache__/
│ │ │ ├─ __init__.cpython-312.pyc
│ │ │ └─ dependencies.cpython-312.pyc
│ │ ├─ dependencies.py
│ │ └─ __init__.py
│ ├─ routes/
│ │ routes/
│ │ ├─ task_routes.py
│ │ ├─ auth_routes.py
│ │ ├─ __pycache__/
│ │ │ __pycache__/
│ │ │ ├─ task_routes.cpython-312.pyc
│ │ │ ├─ user_management_routes.cpython-312.pyc
│ │ │ ├─ __init__.cpython-312.pyc
│ │ │ └─ auth_routes.cpython-312.pyc
│ │ ├─ user_management_routes.py
│ │ └─ __init__.py
│ └─ alembic.ini
├─ alembic.ini
└─ .dockerignore
File Contents:
File: FastAPI/requirements.txt
==============================
alembic==1.13.3
annotated-types==0.7.0
anyio==4.6.0
astroid==3.3.5
black==24.10.0
cffi==1.17.1
click==8.1.7
cryptography==43.0.1
dill==0.3.9
dnspython==2.7.0
ecdsa==0.19.0
email_validator==2.2.0
fastapi==0.115.0
greenlet==3.1.1
h11==0.14.0
idna==3.10
isort==5.13.2
python-multipart==0.0.5
Mako==1.3.5
MarkupSafe==3.0.1
mccabe==0.7.0
mypy-extensions==1.0.0
packaging==24.1
passlib==1.7.4
pathspec==0.12.1
peewee==3.17.6
platformdirs==4.3.6
pyasn1==0.6.1
pycparser==2.22
pydantic==2.9.2
pydantic_core==2.23.4
pylint==3.3.1
PyMySQL==1.1.1
python-dotenv==1.0.1
python-jose==3.3.0
rsa==4.9
six==1.16.0
sniffio==1.3.1
SQLAlchemy==2.0.35
starlette==0.38.6
tomlkit==0.13.2
typing_extensions==4.12.2
uvicorn==0.31.0
File: FastAPI/Dockerfile
========================
FROM python:3.12
WORKDIR /app
RUN apt-get update && apt-get upgrade -y && apt-get install -y mariadb-client &&
pip install mysqlclient
File: FastAPI/app/models/role.py
================================
"""
This module defines the Role class, which represents a role model used to manage
different roles or permissions within an application.
The Role class includes attributes such as the role's unique identifier and name.
class Role(BaseModel):
"""
Represents a role with relevant details.
Attributes:
-----------
id : int
Unique identifier for the role.
name : str
The name of the role (e.g., 'admin', 'user'),
representing the permissions associated with it.
"""
id: int
name: str
File: FastAPI/app/models/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.
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 Config:
from_attributes = True # Cambia 'orm_mode' a 'from_attributes' para
Pydantic v2
class TaskCreate(BaseModel):
title: str
description: Optional[str] = None
expiration_date: Optional[date] = None
status_id: int
class TaskUpdate(BaseModel):
"""
This is so that the task can be updated.
"""
title: Optional[str] = None
description: Optional[str] = None
expiration_date: Optional[date] = None
status_id: Optional[int] = None
is_favorite: Optional[bool] = None
File: FastAPI/app/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.
class Status(BaseModel):
"""
Represents a status with relevant details.
Attributes:
-----------
id : int
Unique identifier for the status.
name : str
The name of the status (e.g., 'pending', 'completed', 'in progress'),
describing the current state of an entity.
"""
id: int
name: str
File: FastAPI/app/models/user.py
================================
from typing import Optional
from pydantic import BaseModel, EmailStr
from .role import Role
class UserRead(BaseModel):
id: int
email: EmailStr
role_id: int
is_active: bool
role: Role
class Config:
from_attributes = True
class UserCreate(BaseModel):
email: EmailStr
password: str # Contraseña en texto plano; se hashea antes de almacenar
role_id: int
class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
password: Optional[str] = None
role_id: Optional[int] = None
File: FastAPI/app/models/__pycache__/change.cpython-312.pyc
===========================================================
Contenido binario o no legible.
File: FastAPI/app/models/__pycache__/task.cpython-312.pyc
=========================================================
Contenido binario o no legible.
File: FastAPI/app/models/__pycache__/__init__.cpython-312.pyc
=============================================================
Contenido binario o no legible.
File: FastAPI/app/models/__pycache__/role.cpython-312.pyc
=========================================================
Contenido binario o no legible.
File: FastAPI/app/models/__pycache__/user.cpython-312.pyc
=========================================================
Contenido binario o no legible.
File: FastAPI/app/models/__init__.py
====================================
File: FastAPI/app/models/change.py
==================================
"""
Model for logging changes to tasks.
"""
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
File: FastAPI/app/config/__pycache__/database.cpython-312.pyc
=============================================================
Contenido binario o no legible.
File: FastAPI/app/config/__pycache__/__init__.cpython-312.pyc
=============================================================
Contenido binario o no legible.
File: FastAPI/app/config/__pycache__/settings.cpython-312.pyc
=============================================================
Contenido binario o no legible.
File: FastAPI/app/config/database.py
====================================
"""
Necessary imports to define data models using Peewee ORM.
- Model: Base class for all models.
- CharField, TextField, DateField, etc.: Database field types.
- ForeignKeyField: Defines relationships between tables.
- Check: Defines column-level constraints.
- AutoField: Auto-incrementing field used as a primary key.
- fn: Provides SQL functions like CURRENT_TIMESTAMP.
"""
from datetime import date, datetime # Importa datetime
from dotenv import load_dotenv
from config.settings import DATABASE
from peewee import (
MySQLDatabase, Model, CharField, TextField, DateField,
ForeignKeyField, BooleanField, DateTimeField, Check, AutoField, fn, SQL
)
class RoleModel(Model):
"""Role table."""
id = AutoField(primary_key=True)
name = CharField(null=False)
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 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 ChangeModel(Model):
"""Change table to log modifications in Task."""
id = AutoField(primary_key=True)
task = ForeignKeyField(TaskModel, backref='changes', on_delete='CASCADE')
timestamp = DateTimeField(default=datetime.utcnow)
field_changed = CharField(null=False)
old_value = TextField(null=True)
new_value = TextField(null=True)
File: FastAPI/app/config/__init__.py
====================================
File: FastAPI/app/config/settings.py
====================================
"""
This module loads the environment configuration for the application.
Uses `dotenv` to load environment variables from a `.env` file.
Defines the database configuration based on the application's environment.
Variables:
ENV (str): Specifies the application environment. Defaults to 'dev' if not set.
DATABASE (dict): Dictionary containing the database configuration.
- name: Name of the database.
- engine: Database engine used.
- user: Username for the database connection.
- password: Password for the database connection.
- host: Host address of the database.
- port: Port used for the database connection.
"""
import os
from dotenv import load_dotenv
if ENV == "production":
DATABASE = {
"name": os.getenv("MYSQL_DATABASE"),
"engine": "peewee.MySQLDatabase", # MySQL as the database engine
"user": os.getenv("MYSQL_USER"),
"password": os.getenv("MYSQL_PASSWORD"),
"host": os.getenv("MYSQL_HOST"),
"port": int(os.getenv("MYSQL_PORT")),
}
else:
DATABASE = {
"name": os.getenv("MYSQL_DATABASE"),
"engine": "peewee.MySQLDatabase", # MySQL as the database engine
"user": os.getenv("MYSQL_USER"),
"password": os.getenv("MYSQL_PASSWORD"),
"host": os.getenv("MYSQL_HOST"),
"port": int(os.getenv("MYSQL_PORT")),
}
# Configuración de JWT
SECRET_KEY = os.getenv("SECRET_KEY")
ALGORITHM = os.getenv("ALGORITHM", "HS256")
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30"))
File: FastAPI/app/__pycache__/database.cpython-312.pyc
======================================================
Contenido binario o no legible.
File: FastAPI/app/__pycache__/main.cpython-312.pyc
==================================================
Contenido binario o no legible.
File: FastAPI/app/__pycache__/migrations.cpython-312.pyc
========================================================
Contenido binario o no legible.
File: FastAPI/app/migrations.py
===============================
"""
Necessary imports to define data models using SQLAlchemy ORM.
- Column: Defines a column in a table.
- Integer: Integer data type for columns.
- String: Variable-length string data type.
- Text: Large text data type for columns.
- Date: Date data type for columns.
- Boolean: Boolean data type for columns.
- ForeignKey: Defines a foreign key constraint.
- CheckConstraint: Adds a check constraint to a column.
- declarative_base: Base class for declarative model definitions.
- relationship: Establishes relationships between models.
- sessionmaker: Factory for creating new Session objects.
- create_engine: Creates a new SQLAlchemy Engine instance.
"""
from sqlalchemy import Column, Integer, String, Text, Date, Boolean, ForeignKey,
CheckConstraint
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy import create_engine
from config.settings import DATABASE
__table_args__ = (
CheckConstraint("name IN ('TO_DO', 'IN_PROGRESS', 'COMPLETED')",
name="check_status_name"),
)
File:
FastAPI/app/alembic/versions/__pycache__/905731c6a76a_first_migrations.cpython-
312.pyc
===================================================================================
=========
Contenido binario o no legible.
File:
FastAPI/app/alembic/versions/__pycache__/6655c8f0dad7_initial_migration.cpython-
312.pyc
===================================================================================
==========
Contenido binario o no legible.
File:
FastAPI/app/alembic/versions/__pycache__/d89dc7a9d248_initial_migration.cpython-
312.pyc
===================================================================================
==========
Contenido binario o no legible.
File: FastAPI/app/alembic/versions/905731c6a76a_first_migrations.py
===================================================================
"""first_migrations
"""
from typing import Sequence, Union
op.execute("""
DELETE FROM role WHERE name IN ('Administrator', 'User');
""")
File: FastAPI/app/alembic/env.py
================================
"""
Este módulo configura Alembic para manejar migraciones de base de datos.
File: FastAPI/app/alembic/script.py.mako
========================================
Contenido binario o no legible.
File: FastAPI/app/alembic/__pycache__/env.cpython-312.pyc
=========================================================
Contenido binario o no legible.
File: FastAPI/app/alembic/README
================================
Contenido binario o no legible.
File: FastAPI/app/services/__pycache__/user_service.cpython-312.pyc
===================================================================
Contenido binario o no legible.
File: FastAPI/app/services/__pycache__/__init__.cpython-312.pyc
===============================================================
Contenido binario o no legible.
File: FastAPI/app/services/__pycache__/auth_service.cpython-312.pyc
===================================================================
Contenido binario o no legible.
File: FastAPI/app/services/__pycache__/task_service.cpython-312.pyc
===================================================================
Contenido binario o no legible.
File: FastAPI/app/services/auth_service.py
==========================================
"""
Authentication Service for handling password hashing and JWT.
class AuthService:
"""Authentication Service."""
@staticmethod
def verify_password(plain_password: str, password: str) -> bool:
"""Verifica si la contraseña en texto plano coincide con la contraseña
hasheada."""
return pwd_context.verify(plain_password, password)
@staticmethod
def get_password_hash(password: str) -> str:
"""Hashea una contraseña en texto plano antes de almacenarla."""
return pwd_context.hash(password)
@staticmethod
def authenticate_user(email: str, password: str) -> Optional[UserModel]:
"""
Autentica a un usuario utilizando su email y contraseña.
"""
user = UserService.get_user_by_email_login(email)
if not user or not AuthService.verify_password(password, user.password): #
Usa `user.password`
return None
return user
@staticmethod
def create_access_token(
data: dict, expires_delta: Optional[timedelta] = None
) -> str:
"""
Crea un token de acceso JWT.
"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() +
timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@staticmethod
def get_current_user(token: str) -> Optional[UserRead]:
"""
Decodes the JWT token and retrieves the user based on the email.
"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# Decodifica el token JWT
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
email: str = payload.get("sub")
if email is None:
raise credentials_exception
except JWTError:
raise credentials_exception
File: FastAPI/app/services/__init__.py
======================================
File: FastAPI/app/services/user_service.py
==========================================
"""
Service layer for User operations.
class UserService:
"""Service layer for User operations."""
@staticmethod
def create_user(email: str, password: str, role_id: int) -> UserRead:
"""
Crear un nuevo usuario.
"""
try:
role_instance = RoleModel.get_by_id(role_id)
user_instance = UserModel.create(email=email, password=password,
role=role_instance)
return UserRead.model_validate(user_data)
except DoesNotExist as exc:
raise ValueError(f"Role with id {role_id} not found") from exc
@staticmethod
def get_user_by_email_login(email: str) -> Optional[UserModel]:
"""
Recupera el usuario completo desde la base de datos por su email.
"""
try:
return UserModel.get(UserModel.email == email)
except UserModel.DoesNotExist:
return None
@staticmethod
def get_user_by_email(email: str) -> Optional[UserRead]:
"""
Obtener un usuario por su email.
"""
try:
user_instance = UserModel.get(UserModel.email == email)
user_data = user_instance.__data__.copy()
user_data['role_id'] = user_instance.role.id # Añadir role_id
explícitamente
@staticmethod
def get_user_by_id(user_id: int) -> Optional[UserRead]:
"""
Obtener un usuario por su ID.
"""
try:
user_instance = UserModel.get_by_id(user_id)
user_data = user_instance.__data__.copy()
user_data['role_id'] = user_instance.role.id # Añadir role_id
explícitamente
return UserRead.model_validate(user_data)
except DoesNotExist:
return None
@staticmethod
def update_user_status(user_id: int, is_active: bool) -> bool:
"""
Actualiza el estado activo de un usuario.
"""
try:
user_instance = UserModel.get_by_id(user_id)
user_instance.is_active = is_active
user_instance.save()
return True
except DoesNotExist:
return False
@staticmethod
def update_user(
user_id: int,
email: Optional[str] = None,
password: Optional[str] = None,
role_id: Optional[int] = None,
) -> Optional[UserRead]:
"""
Actualizar un usuario existente por su 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 UserRead.model_validate(user_data)
except DoesNotExist:
return None
@staticmethod
def delete_user(user_id: int) -> bool:
"""
Eliminar un usuario por su 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[UserRead]:
"""
Obtener una lista de todos los usuarios.
"""
users = UserModel.select().where(UserModel.role == 2)
return users
File: FastAPI/app/services/task_service.py
==========================================
"""
Module defining services for managing tasks.
"""
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,
) -> TaskModel: # Devuelve el modelo de la base de datos
"""Creates a new task."""
task = TaskModel.create( # Asegúrate de usar el modelo ORM aquí
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[TaskModel]:
"""Retrieves a task by ID, checking user permissions."""
task = TaskModel.get_or_none(TaskModel.id == task_id) # Usa el modelo ORM
aquí
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 = TaskModel.get_or_none(TaskModel.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()
@staticmethod
def delete_task(task_id: int, user_id: int, is_admin: bool) -> bool:
"""Deletes a task, checking user permissions."""
task = TaskModel.get_or_none(TaskModel.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 = TaskModel.select().where((TaskModel.user_id == user_id) |
(is_admin))
if status:
query = query.where(TaskModel.status_id == status)
if expiration_date:
query = query.where(TaskModel.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 = TaskModel.get_or_none(TaskModel.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(
ChangeModel.select()
.where(ChangeModel.task == task_id)
.order_by(ChangeModel.timestamp.desc())
)
File: FastAPI/app/main.py
=========================
"""
Main module for the FastAPI application.
"""
@asynccontextmanager
async def lifespan(api: FastAPI):
if connection.is_closed():
connection.connect()
try:
yield
finally:
if not connection.is_closed():
connection.close()
app = FastAPI(lifespan=lifespan)
@app.get("/")
async def docs_redirect():
"""Redirige a la documentación de la API."""
return RedirectResponse(url="/docs")
app.include_router(auth_router)
app.include_router(task_router)
app.include_router(user_management_router)
File: FastAPI/app/utils/__pycache__/__init__.cpython-312.pyc
============================================================
Contenido binario o no legible.
File: FastAPI/app/utils/__pycache__/dependencies.cpython-312.pyc
================================================================
Contenido binario o no legible.
File: FastAPI/app/utils/dependencies.py
=======================================
"""
Security dependencies for authentication and authorization handling.
Includes functions to obtain the current user and verify roles.
"""
from fastapi import Depends, HTTPException, status, Form
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")
class OAuth2PasswordRequestFormEmail:
def __init__(self, email: str = Form(...), password: str = Form(...)):
self.email = email
self.password = password
File: FastAPI/app/utils/__init__.py
===================================
File: FastAPI/app/routes/task_routes.py
=======================================
"""
Module defining routes for managing tasks.
router = APIRouter(
prefix="/tasks",
tags=["tasks"],
)
@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED)
def create_task(
task_data: TaskCreate,
current_user: UserRead = Depends(get_current_user)
) -> Task:
task_model = TaskService.create_task(
title=task_data.title,
description=task_data.description,
expiration_date=task_data.expiration_date,
status_id=task_data.status_id,
user_id=current_user.id
)
# Convierte el modelo ORM a un modelo Pydantic
return Task.from_orm(task_model)
@router.get("/{task_id}", response_model=Task)
def get_task(
task_id: int,
current_user: UserRead = 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.from_orm(task) # Convierte de ORM a modelo Pydantic
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: UserRead = Depends(get_current_user) # Cambiado de User a
UserRead
) -> 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: UserRead = Depends(get_current_user) # Cambiado de User a
UserRead
):
"""
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: UserRead = Depends(get_current_user) # Cambiado de User a
UserRead
) -> 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: UserRead = Depends(get_current_user) # Cambiado de User a
UserRead
) -> 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: UserRead = Depends(get_current_user) # Cambiado de User a
UserRead
) -> 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 changes
File: FastAPI/app/routes/auth_routes.py
=======================================
"""
Module defining authentication routes.
router = APIRouter(
tags=["Authentication"],
)
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
email: Optional[EmailStr] = None
@router.post("/register", response_model=UserRead,
status_code=status.HTTP_201_CREATED)
def register(user: UserCreate):
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(form_data: OAuth2PasswordRequestFormEmail = Depends()):
# Autenticación usando el email
user = AuthService.authenticate_user(form_data.email, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = AuthService.create_access_token(
data={"sub": user.email} # Guarda el email en el token
)
return {"access_token": access_token, "token_type": "bearer"}
File: FastAPI/app/routes/__pycache__/task_routes.cpython-312.pyc
================================================================
Contenido binario o no legible.
File: FastAPI/app/routes/__pycache__/user_management_routes.cpython-312.pyc
===========================================================================
Contenido binario o no legible.
File: FastAPI/app/routes/__pycache__/__init__.cpython-312.pyc
=============================================================
Contenido binario o no legible.
File: FastAPI/app/routes/__pycache__/auth_routes.cpython-312.pyc
================================================================
Contenido binario o no legible.
File: FastAPI/app/routes/user_management_routes.py
==================================================
"""
Module that defines routes for managing users.
router = APIRouter(
prefix="/admin/users",
tags=["admin users"],
)
@router.get("/", response_model=List[UserRead])
def list_users() -> List[UserRead]:
"""List all registered users."""
return UserService.list_all_users()
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=UserRead)
def get_user(
user_id: int,
current_admin: UserRead = Depends(get_current_admin) # Cambiar tipo a UserRead
) -> UserRead:
"""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=UserRead)
def update_user(
user_id: int,
user_update: UserUpdate,
current_admin: UserRead = Depends(get_current_admin) # Cambiar tipo a UserRead
) -> UserRead:
"""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: UserRead = Depends(get_current_admin) # Cambiar tipo a UserRead
):
"""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: UserRead = Depends(get_current_admin) # Cambiar tipo a UserRead
):
"""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")
File: FastAPI/app/routes/__init__.py
====================================
"""
init.py
"""
from .auth_routes import router as auth_router
from .task_routes import router as task_router
from .user_management_routes import router as user_management_router
File: FastAPI/app/alembic.ini
=============================
Contenido binario o no legible.
File: FastAPI/alembic.ini
=========================
Contenido binario o no legible.
File: FastAPI/.dockerignore
===========================
Contenido binario o no legible.