Backend totalmente hecho con mongodb, añadida la funcionalidad de usuarios con historial
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
DB_USER = "root"
|
||||
DB_PASSWORD = "root"
|
||||
DB_HOST = "db"
|
||||
DB_PORT = "3306"
|
||||
DB_NAME = "deckofcards"
|
||||
|
||||
DATABASE_URL = (
|
||||
f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
||||
)
|
||||
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
pool_pre_ping=True,
|
||||
echo=False
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
from .connection import engine
|
||||
from .models import Base
|
||||
|
||||
def init_db():
|
||||
Base.metadata.create_all(bind=engine)
|
||||
@@ -1,14 +0,0 @@
|
||||
from sqlalchemy.orm import declarative_base
|
||||
from sqlalchemy import Column, Integer, String, Float
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class DoCMFLevel(Base):
|
||||
__tablename__ = "docmf_levels"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
term = Column(String(50), nullable=False)
|
||||
core_a = Column(Float, nullable=False)
|
||||
core_b = Column(Float, nullable=False)
|
||||
support_c = Column(Float, nullable=False)
|
||||
support_d = Column(Float, nullable=False)
|
||||
@@ -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"]
|
||||
@@ -1,15 +0,0 @@
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from .connection import engine
|
||||
|
||||
SessionLocal = sessionmaker(
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine
|
||||
)
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
+11
-7
@@ -1,23 +1,24 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from api.database.init_db import init_db
|
||||
from api.database.mongodb import db
|
||||
|
||||
# Routers
|
||||
from api.routers.test_db import router as test_db_router
|
||||
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):
|
||||
init_db()
|
||||
# Aquí podrías hacer comprobaciones si quieres
|
||||
yield
|
||||
|
||||
# No hace falta cerrar nada con Motor
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
@@ -29,9 +30,12 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(test_db_router, prefix="/api")
|
||||
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")
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -1,13 +0,0 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import text
|
||||
from api.database.session import get_db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/test-db")
|
||||
def test_db_connection(db=Depends(get_db)):
|
||||
try:
|
||||
db.execute(text("SELECT 1"))
|
||||
return {"status": "ok", "message": "Conexión a MySQL correcta"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
@@ -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)}
|
||||
@@ -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
|
||||
@@ -1,6 +1,7 @@
|
||||
fastapi
|
||||
uvicorn
|
||||
sqlalchemy
|
||||
pymysql
|
||||
motor
|
||||
pydantic
|
||||
cryptography
|
||||
passlib
|
||||
bcrypt==4.0.1
|
||||
email-validator
|
||||
|
||||
Reference in New Issue
Block a user