Merge pull request #13 from AlexisLopez-Dev/feature/backend-v2-mongo
Feature/backend v2 mongo
This commit is contained in:
+6
-2
@@ -2,6 +2,10 @@ FROM python:3.10-slim
|
|||||||
|
|
||||||
WORKDIR /app
|
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"]
|
||||||
|
|||||||
@@ -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
@@ -1,14 +1,26 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
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
|
# Routers
|
||||||
from routers.docmf_build import router as docmf_build_router
|
from api.routers.test_mongo import router as test_mongo_router
|
||||||
from routers.docmf_evaluate import router as docmf_eval_router
|
from api.routers.value_function import router as value_router
|
||||||
from routers.docmf_simple_validation import router as simple_validation_router
|
from api.routers.docmf_build import router as docmf_build_router
|
||||||
from routers.docmf_validation import router as validation_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(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
@@ -18,8 +30,12 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.include_router(test_mongo_router, prefix="/api")
|
||||||
app.include_router(value_router, prefix="/api/criteria/doc")
|
app.include_router(value_router, prefix="/api/criteria/doc")
|
||||||
app.include_router(docmf_build_router, prefix="/api/criteria/doc-mf")
|
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(docmf_eval_router, prefix="/api/criteria/doc-mf")
|
||||||
app.include_router(simple_validation_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(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")
|
||||||
@@ -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] = []
|
||||||
|
|
||||||
@@ -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"}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from models.docmf_models import DoCMFMultiRequest
|
from api.models.docmf_models import DoCMFMultiRequest
|
||||||
from services.docmf_build_service import build_docmf_multi
|
from api.services.docmf_build_service import build_docmf_multi
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from models.evaluation_models import EvaluationRequest
|
from api.models.evaluation_models import EvaluationRequest
|
||||||
from services.docmf_evaluate_service import evaluate_docmf
|
from api.services.docmf_evaluate_service import evaluate_docmf
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from models.docmf_simple_validation_models import SimpleValidationRequest
|
from api.models.docmf_simple_validation_models import SimpleValidationRequest
|
||||||
from services.docmf_simple_validation_service import validate_simple_levels
|
from api.services.docmf_simple_validation_service import validate_simple_levels
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from models.docmf_validation_models import ValidationRequest
|
from api.models.docmf_validation_models import ValidationRequest
|
||||||
from services.docmf_validation_service import validate_levels
|
from api.services.docmf_validation_service import validate_levels
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -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"}
|
||||||
@@ -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)}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from models.value_function_models import ValueFunctionRequest
|
from api.models.value_function_models import ValueFunctionRequest
|
||||||
from services.value_function_service import compute_value_function, compute_points
|
from api.services.value_function_service import compute_value_function, compute_points
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from utils.interpolation import linear_interpolation
|
from api.utils.interpolation import linear_interpolation
|
||||||
|
|
||||||
def evaluate_docmf(request):
|
def evaluate_docmf(request):
|
||||||
x = request.x
|
x = request.x
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
motor
|
||||||
|
pydantic
|
||||||
|
passlib
|
||||||
|
bcrypt==4.0.1
|
||||||
|
email-validator
|
||||||
+5
-14
@@ -11,12 +11,6 @@ services:
|
|||||||
- ./backend:/app
|
- ./backend:/app
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
environment:
|
|
||||||
DB_HOST: db
|
|
||||||
DB_PORT: 3306
|
|
||||||
DB_USER: root
|
|
||||||
DB_PASSWORD: root
|
|
||||||
DB_NAME: deckofcards
|
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
@@ -29,16 +23,13 @@ services:
|
|||||||
- /app/node_modules
|
- /app/node_modules
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: mysql:8.0
|
image: mongo:6
|
||||||
container_name: mysql_db
|
container_name: mongo
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
|
||||||
MYSQL_ROOT_PASSWORD: root
|
|
||||||
MYSQL_DATABASE: deckofcards
|
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "27018:27017"
|
||||||
volumes:
|
volumes:
|
||||||
- mysql_data:/var/lib/mysql
|
- mongo_data:/data/db
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql_data:
|
mongo_data:
|
||||||
|
|||||||
Reference in New Issue
Block a user