Merge pull request #13 from AlexisLopez-Dev/feature/backend-v2-mongo

Feature/backend v2 mongo
This commit is contained in:
Mireya Cueto Garrido
2026-03-27 11:17:59 +01:00
committed by GitHub
16 changed files with 283 additions and 33 deletions
+6 -2
View File
@@ -2,6 +2,10 @@ FROM python:3.10-slim
WORKDIR /app
RUN pip install fastapi uvicorn
COPY requirements.txt .
CMD ["uvicorn", "api.routes:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
+9
View File
@@ -0,0 +1,9 @@
from motor.motor_asyncio import AsyncIOMotorClient
MONGO_URL = "mongodb://mongo:27017"
DB_NAME = "deckofcards"
client = AsyncIOMotorClient(MONGO_URL)
db = client[DB_NAME]
users_collection = db["users"]
+22 -6
View File
@@ -1,14 +1,26 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from api.database.mongodb import db
from routers.value_function import router as value_router
from routers.docmf_build import router as docmf_build_router
from routers.docmf_evaluate import router as docmf_eval_router
from routers.docmf_simple_validation import router as simple_validation_router
from routers.docmf_validation import router as validation_router
# Routers
from api.routers.test_mongo import router as test_mongo_router
from api.routers.value_function import router as value_router
from api.routers.docmf_build import router as docmf_build_router
from api.routers.docmf_evaluate import router as docmf_eval_router
from api.routers.docmf_simple_validation import router as simple_validation_router
from api.routers.docmf_validation import router as validation_router
from api.routers.auth import router as auth_router
from api.routers.history import router as history_router
from api.routers.test_mongo import router as test_mongo_router
@asynccontextmanager
async def lifespan(app: FastAPI):
# Aquí podrías hacer comprobaciones si quieres
yield
# No hace falta cerrar nada con Motor
app = FastAPI()
app = FastAPI(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
@@ -18,8 +30,12 @@ app.add_middleware(
allow_headers=["*"],
)
app.include_router(test_mongo_router, prefix="/api")
app.include_router(value_router, prefix="/api/criteria/doc")
app.include_router(docmf_build_router, prefix="/api/criteria/doc-mf")
app.include_router(docmf_eval_router, prefix="/api/criteria/doc-mf")
app.include_router(simple_validation_router, prefix="/api/criteria/doc-mf")
app.include_router(validation_router, prefix="/api/criteria/doc-mf")
app.include_router(test_mongo_router, prefix="/api")
app.include_router(auth_router, prefix="/api")
app.include_router(history_router, prefix="/api")
+44
View File
@@ -0,0 +1,44 @@
from typing import List, Optional
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
class FuzzyTerm(BaseModel):
term: str
core: List[float]
support: List[float]
left_nodes: List[List[float]]
right_nodes: List[List[float]]
class HistoryItem(BaseModel):
id: Optional[str] = Field(default=None, alias="_id")
name: str
created_at: datetime
results: List[FuzzyTerm]
class HistoryCreateRequest(BaseModel):
name: str
results: List[FuzzyTerm]
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class UserLogin(BaseModel):
email: EmailStr
password: str
class UserInDB(BaseModel):
id: Optional[str] = Field(default=None, alias="_id")
username: str
email: EmailStr
password_hash: str
token: Optional[str] = None
history: List[HistoryItem] = []
+89
View File
@@ -0,0 +1,89 @@
from fastapi import APIRouter, HTTPException, status
from api.database.mongodb import users_collection
from api.models.user_models import UserCreate, UserLogin
from api.utils.security import hash_password, verify_password, generate_token
from bson import ObjectId
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/register")
async def register_user(user: UserCreate):
existing_username = await users_collection.find_one({"username": user.username})
if existing_username:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="El nombre de usuario ya está en uso",
)
existing_email = await users_collection.find_one({"email": user.email})
if existing_email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="El email ya está registrado",
)
token = generate_token()
user_doc = {
"username": user.username,
"email": user.email,
"password_hash": hash_password(user.password),
"token": token,
"history": [],
}
result = await users_collection.insert_one(user_doc)
return {
"message": "Usuario registrado correctamente",
"user_id": str(result.inserted_id),
"token": token,
}
@router.post("/login")
async def login_user(credentials: UserLogin):
user = await users_collection.find_one({"email": credentials.email})
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Credenciales inválidas",
)
if not verify_password(credentials.password, user["password_hash"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Credenciales inválidas",
)
new_token = generate_token()
await users_collection.update_one(
{"_id": user["_id"]},
{"$set": {"token": new_token}}
)
return {
"message": "Login correcto",
"user_id": str(user["_id"]),
"username": user["username"],
"token": new_token,
}
@router.post("/logout/{user_id}")
async def logout_user(user_id: str):
user = await users_collection.find_one({"_id": ObjectId(user_id)})
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Usuario no encontrado",
)
await users_collection.update_one(
{"_id": ObjectId(user_id)},
{"$set": {"token": None}}
)
return {"message": "Sesión cerrada correctamente"}
+2 -2
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException
from models.docmf_models import DoCMFMultiRequest
from services.docmf_build_service import build_docmf_multi
from api.models.docmf_models import DoCMFMultiRequest
from api.services.docmf_build_service import build_docmf_multi
router = APIRouter()
+2 -2
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException
from models.evaluation_models import EvaluationRequest
from services.docmf_evaluate_service import evaluate_docmf
from api.models.evaluation_models import EvaluationRequest
from api.services.docmf_evaluate_service import evaluate_docmf
router = APIRouter()
@@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException
from models.docmf_simple_validation_models import SimpleValidationRequest
from services.docmf_simple_validation_service import validate_simple_levels
from api.models.docmf_simple_validation_models import SimpleValidationRequest
from api.services.docmf_simple_validation_service import validate_simple_levels
router = APIRouter()
+2 -2
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException
from models.docmf_validation_models import ValidationRequest
from services.docmf_validation_service import validate_levels
from api.models.docmf_validation_models import ValidationRequest
from api.services.docmf_validation_service import validate_levels
router = APIRouter()
+62
View File
@@ -0,0 +1,62 @@
from fastapi import APIRouter, HTTPException, status
from typing import List
from datetime import datetime
from bson import ObjectId
from api.database.mongodb import users_collection
from api.models.user_models import FuzzyTerm, HistoryCreateRequest
router = APIRouter(prefix="/history", tags=["history"])
@router.post("/{user_id}/add")
async def add_history_item(user_id: str, data: HistoryCreateRequest):
user = await users_collection.find_one({"_id": ObjectId(user_id)})
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Usuario no encontrado",
)
history_item_id = ObjectId()
history_item = {
"_id": history_item_id,
"name": data.name, # ← nuevo campo
"created_at": datetime.utcnow(),
"results": [r.dict() for r in data.results],
}
await users_collection.update_one(
{"_id": ObjectId(user_id)},
{"$push": {"history": history_item}},
)
return {
"message": "Elemento añadido al historial",
"history_item_id": str(history_item_id),
}
@router.delete("/{user_id}/delete/{history_item_id}")
async def delete_history_item(user_id: str, history_item_id: str):
user = await users_collection.find_one({"_id": ObjectId(user_id)})
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Usuario no encontrado",
)
result = await users_collection.update_one(
{"_id": ObjectId(user_id)},
{"$pull": {"history": {"_id": ObjectId(history_item_id)}}},
)
if result.modified_count == 0:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Elemento de historial no encontrado",
)
return {"message": "Elemento eliminado del historial"}
+12
View File
@@ -0,0 +1,12 @@
from fastapi import APIRouter
from api.database.mongodb import db
router = APIRouter()
@router.get("/test-mongo")
async def test_mongo():
try:
await db.command("ping")
return {"status": "ok", "message": "Conexión a MongoDB correcta"}
except Exception as e:
return {"status": "error", "message": str(e)}
+2 -2
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, HTTPException
from models.value_function_models import ValueFunctionRequest
from services.value_function_service import compute_value_function, compute_points
from api.models.value_function_models import ValueFunctionRequest
from api.services.value_function_service import compute_value_function, compute_points
router = APIRouter()
@@ -1,4 +1,4 @@
from utils.interpolation import linear_interpolation
from api.utils.interpolation import linear_interpolation
def evaluate_docmf(request):
x = request.x
+16
View File
@@ -0,0 +1,16 @@
import secrets
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def generate_token() -> str:
return secrets.token_hex(32) # 64 caracteres seguros
+7
View File
@@ -0,0 +1,7 @@
fastapi
uvicorn
motor
pydantic
passlib
bcrypt==4.0.1
email-validator
+5 -14
View File
@@ -11,12 +11,6 @@ services:
- ./backend:/app
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 3306
DB_USER: root
DB_PASSWORD: root
DB_NAME: deckofcards
frontend:
build:
@@ -29,16 +23,13 @@ services:
- /app/node_modules
db:
image: mysql:8.0
container_name: mysql_db
image: mongo:6
container_name: mongo
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: deckofcards
ports:
- "3306:3306"
- "27018:27017"
volumes:
- mysql_data:/var/lib/mysql
- mongo_data:/data/db
volumes:
mysql_data:
mongo_data: