frontend añadido y backend de creacion de notas
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from backend.services.notaService import NotaService
|
||||
from backend.schemas.notaSchema import NotaCreate, NotaUpdate, NotaOut, SearchQuery, HybridSearchQuery
|
||||
from backend.db.dependencies import get_db # tu función para obtener la sesión
|
||||
|
||||
router = APIRouter(prefix="/notas", tags=["Notas"])
|
||||
|
||||
|
||||
@router.post("/", response_model=str)
|
||||
def create_nota(data: NotaCreate, db: Session = Depends(get_db)):
|
||||
return NotaService(db).create(**data.dict())
|
||||
|
||||
|
||||
@router.get("/{id_}", response_model=NotaOut)
|
||||
def get_nota(id_: str, db: Session = Depends(get_db)):
|
||||
return NotaService(db).get(id_)
|
||||
|
||||
|
||||
@router.get("/", response_model=list[NotaOut])
|
||||
def list_notas(db: Session = Depends(get_db)):
|
||||
return NotaService(db).list_all()
|
||||
|
||||
|
||||
@router.put("/{id_}", response_model=bool)
|
||||
def update_nota(id_: str, data: NotaUpdate, db: Session = Depends(get_db)):
|
||||
return NotaService(db).update(id_, **data.dict(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/{id_}", response_model=bool)
|
||||
def delete_nota(id_: str, db: Session = Depends(get_db)):
|
||||
return NotaService(db).delete(id_)
|
||||
|
||||
|
||||
# ---------------------
|
||||
# RAG Endpoints
|
||||
# ---------------------
|
||||
@router.post("/search", response_model=list[NotaOut])
|
||||
def search_nota(query: SearchQuery, db: Session = Depends(get_db)):
|
||||
return NotaService(db).search_by_text(query.texto, query.top_k)
|
||||
|
||||
|
||||
@router.post("/hybrid-search", response_model=list[NotaOut])
|
||||
def hybrid_search(query: HybridSearchQuery, db: Session = Depends(get_db)):
|
||||
return NotaService(db).hybrid_search(query.texto, query.tags, query.top_k)
|
||||
@@ -0,0 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
from backend.api.endpoints.notaEndpoint import router as nota_router
|
||||
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
api_router.include_router(nota_router)
|
||||
@@ -0,0 +1,5 @@
|
||||
# backend/db/base.py
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
# Declarative Base común para todos los modelos ORM
|
||||
Base = declarative_base()
|
||||
@@ -0,0 +1,8 @@
|
||||
from backend.db.session import SessionLocal
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,36 @@
|
||||
# backend/db/session.py
|
||||
import os
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from urllib.parse import quote_plus
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Cargar variables de entorno (.env) desde backend/.env
|
||||
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "..", ".env"))
|
||||
|
||||
DB_USER = os.getenv("DB_USER")
|
||||
DB_PASSWORD = quote_plus(os.getenv("DB_PASSWORD"))
|
||||
DB_HOST = os.getenv("DB_HOST")
|
||||
DB_PORT = os.getenv("DB_PORT", "55455")
|
||||
DB_NAME = os.getenv("DB_NAME")
|
||||
|
||||
DATABASE_URL = (
|
||||
f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
||||
)
|
||||
|
||||
# Crear engine
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
pool_size=5,
|
||||
max_overflow=10,
|
||||
future=True,
|
||||
echo=False, # pon True para ver las queries en consola
|
||||
)
|
||||
|
||||
# Fábrica de sesiones
|
||||
SessionLocal = sessionmaker(
|
||||
bind=engine,
|
||||
autoflush=False,
|
||||
expire_on_commit=False,
|
||||
future=True,
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
from fastapi import FastAPI
|
||||
from backend.api.router import api_router
|
||||
|
||||
app = FastAPI(title="Notes")
|
||||
|
||||
# attach router
|
||||
app.include_router(api_router)
|
||||
@@ -0,0 +1,52 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
|
||||
# ---------------------
|
||||
# Base
|
||||
# ---------------------
|
||||
class NotaBase(BaseModel):
|
||||
titulo: str = Field(..., description="Título de la nota")
|
||||
contenido: str = Field(..., description="Contenido de la nota")
|
||||
tags: Optional[Dict] = Field(None, description="Tags de la nota")
|
||||
relaciones: Optional[Dict] = Field(None, description="Relaciones")
|
||||
propiedades: Optional[Dict] = Field(None, description="Propiedades adicionales")
|
||||
|
||||
|
||||
# ---------------------
|
||||
# Create & Update
|
||||
# ---------------------
|
||||
class NotaCreate(NotaBase):
|
||||
pass
|
||||
|
||||
|
||||
class NotaUpdate(BaseModel):
|
||||
contenido: Optional[str]
|
||||
tags: Optional[Dict]
|
||||
relaciones: Optional[Dict]
|
||||
propiedades: Optional[Dict]
|
||||
|
||||
|
||||
# ---------------------
|
||||
# Response
|
||||
# ---------------------
|
||||
class NotaOut(NotaBase):
|
||||
id: str
|
||||
embedder: List[float] = Field(..., description="Vector embedding de la nota")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ---------------------
|
||||
# Search Inputs
|
||||
# ---------------------
|
||||
class SearchQuery(BaseModel):
|
||||
texto: str
|
||||
top_k: int = 5
|
||||
|
||||
|
||||
class HybridSearchQuery(BaseModel):
|
||||
texto: str
|
||||
tags: Optional[Dict] = None
|
||||
top_k: int = 5
|
||||
@@ -0,0 +1,75 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
|
||||
from domains.nota import notaDom, notaRepo
|
||||
from domains.embedder import NomicEmbedder
|
||||
|
||||
|
||||
class NotaService:
|
||||
def __init__(self, session: Session):
|
||||
self.repo = notaRepo(session)
|
||||
self.emb = NomicEmbedder.get_instance()
|
||||
|
||||
# -------------------
|
||||
# CRUD
|
||||
# -------------------
|
||||
def create(self, titulo: str, contenido: str, tags: Optional[dict] = None,
|
||||
relaciones: Optional[dict] = None, propiedades: Optional[dict] = None) -> str:
|
||||
embedding = self.emb.embed(contenido)
|
||||
nota = notaDom(titulo, contenido, embedding, tags, relaciones, propiedades)
|
||||
return self.repo.add(nota)
|
||||
|
||||
def get(self, id_: str) -> Optional[notaDom]:
|
||||
return self.repo.get_by_id(id_)
|
||||
|
||||
def list_all(self) -> List[notaDom]:
|
||||
return self.repo.get_all()
|
||||
|
||||
def update(self, id_: str, nuevo_contenido: Optional[str] = None,
|
||||
tags: Optional[dict] = None, relaciones: Optional[dict] = None,
|
||||
propiedades: Optional[dict] = None) -> bool:
|
||||
new_data = {}
|
||||
if nuevo_contenido:
|
||||
new_data["contenido"] = nuevo_contenido
|
||||
new_data["embedder"] = self.emb.embed(nuevo_contenido)
|
||||
if tags: new_data["tags"] = tags
|
||||
if relaciones: new_data["relaciones"] = relaciones
|
||||
if propiedades: new_data["propiedades"] = propiedades
|
||||
return self.repo.update(id_, new_data)
|
||||
|
||||
def delete(self, id_: str) -> bool:
|
||||
return self.repo.soft_delete(id_)
|
||||
|
||||
# -------------------
|
||||
# RAG: búsquedas
|
||||
# -------------------
|
||||
def search_by_text(self, texto: str, top_k: int = 5) -> List[notaDom]:
|
||||
emb = self.emb.embed(texto)
|
||||
sql = """
|
||||
SELECT * FROM public.nota
|
||||
WHERE sys_deleted_at IS NULL
|
||||
ORDER BY embedder <-> :query_emb
|
||||
LIMIT :top_k
|
||||
"""
|
||||
rows = self.repo.session.execute(sql, {"query_emb": emb, "top_k": top_k}).fetchall()
|
||||
return [self.repo.Mapper.from_model(r) for r in rows]
|
||||
|
||||
def search_by_tags(self, tags: dict, offset: int = 0, limit: int = 10) -> List[notaDom]:
|
||||
return self.repo.get_paginated_by_tags(tags, offset, limit)
|
||||
|
||||
def hybrid_search(self, texto: str, tags: Optional[dict] = None, top_k: int = 5) -> List[notaDom]:
|
||||
emb = self.emb.embed(texto)
|
||||
sql = """
|
||||
SELECT * FROM public.nota
|
||||
WHERE sys_deleted_at IS NULL
|
||||
{tags_filter}
|
||||
ORDER BY embedder <-> :query_emb
|
||||
LIMIT :top_k
|
||||
"""
|
||||
tags_filter = "AND tags @> :tags" if tags else ""
|
||||
query = sql.format(tags_filter=tags_filter)
|
||||
params = {"query_emb": emb, "top_k": top_k}
|
||||
if tags:
|
||||
params["tags"] = tags
|
||||
rows = self.repo.session.execute(query, params).fetchall()
|
||||
return [self.repo.Mapper.from_model(r) for r in rows]
|
||||
Reference in New Issue
Block a user