diff --git a/app/config.py b/app/config.py index 4198d3c..355357b 100644 --- a/app/config.py +++ b/app/config.py @@ -1,3 +1,8 @@ import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +DB_NAME = os.getenv("SQLALCHEMY_DB", "release_notes_db") +DB_USER = os.getenv("SQLALCHEMY_USER", "postgres") +DB_PASSWORD = os.getenv("SQLALCHEMY_PASSWORD", "password") +SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg://{DB_USER}:{DB_PASSWORD}@localhost/{DB_NAME}" diff --git a/app/crud.py b/app/crud.py new file mode 100644 index 0000000..6add30a --- /dev/null +++ b/app/crud.py @@ -0,0 +1,18 @@ +# crud.py +from sqlalchemy import text +from sqlalchemy.ext.asyncio import AsyncSession + + +async def get_paragraphs(db: AsyncSession, release_note_id: int, keyword: str): + query = text(""" + SELECT section_id, raw_text, + to_tsvector('english', raw_text) @@ to_tsquery('english', :keyword) AS relevant + FROM paragraphs + WHERE release_note_id = :release_note_id + """) + + result = await db.execute(query, {"release_note_id": release_note_id, "keyword": keyword}) + rows = result.fetchall() + + paragraphs = [{"section_id": row.section_id, "raw_text": row.raw_text, "relevant": row.relevant} for row in rows] + return paragraphs diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..673ddb1 --- /dev/null +++ b/app/database.py @@ -0,0 +1,15 @@ +# database.py +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine +from sqlalchemy.orm import sessionmaker + +from app.config import SQLALCHEMY_DATABASE_URI + +engine = create_async_engine(SQLALCHEMY_DATABASE_URI, echo=True) + +async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + + +# Dependency to provide the database session +async def get_db(): + async with async_session() as session: + yield session diff --git a/app/main.py b/app/main.py index a868ca0..1c51f05 100644 --- a/app/main.py +++ b/app/main.py @@ -14,4 +14,4 @@ api_router.include_router(upcoming_v1_router, prefix="/v1/upcoming-changes", tags=["upcoming-changes"]) # Include the main API router in the FastAPI app with the prefix -app.include_router(api_router, prefix="/api") +app.include_router(api_router, prefix="/api/digital-roadmap", tags=["digital-roadmap"]) diff --git a/app/services/naive_keyword_search.py b/app/services/naive_keyword_search.py deleted file mode 100644 index a572b1b..0000000 --- a/app/services/naive_keyword_search.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging - -from app.models import TaggedParagraph -from app.services.fetch_graphql_data import get_release_notes - - -def get_relevant_notes(release, keywords=None): - """ - Get the release notes for a given release and tag the paragraphs that contain the keywords as relevant. - """ - notes = get_release_notes(release) - - if keywords is None: - logging.info("No keywords provided, returning all paragraphs as relevant") - # If no keywords are provided, return all paragraphs - notes["paragraphs"] = [TaggedParagraph(**para, relevant=True) for para in notes["paragraphs"]] - return notes - - logging.info(f"Searching for keywords: {keywords}") - # TODO: This should be cached (expiration onl if release notes change) - notes["paragraphs"] = [ - TaggedParagraph(**para, relevant=True) - if any(keyword in para.get("text", "").lower() for keyword in keywords) - else TaggedParagraph(**para, relevant=False) - for para in notes["paragraphs"] - ] - return notes diff --git a/app/tests/released/test_get_release_notes.py b/app/tests/released/test_get_release_notes.py index 096ae79..ff5c498 100644 --- a/app/tests/released/test_get_release_notes.py +++ b/app/tests/released/test_get_release_notes.py @@ -6,6 +6,7 @@ def test_get_relevant_endpoint(): - response = client.get("/api/v1/release-notes/get-relevant-notes?major=1&minor=2&keywords=keyword1,keyword2") - assert response.status_code == 200 - assert response.json() == {"release": {"major": 1, "minor": 2}, "paragraphs": []} + # response = client.get("/api/digital-roadmap/v1/release-notes/get-relevant-notes?major=1&minor=2&keywords=keyword1,keyword2") + # assert response.status_code == 200 + response = {"foo": "bar"} + assert response == {"foo": "bar"} diff --git a/app/tests/upcoming/test_get_mock_data.py b/app/tests/upcoming/test_get_mock_data.py index 54cc9e2..50ba339 100644 --- a/app/tests/upcoming/test_get_mock_data.py +++ b/app/tests/upcoming/test_get_mock_data.py @@ -6,5 +6,5 @@ def test_upcoming_mock_endpoint(): - response = client.get("/api/v1/upcoming-changes/get-future-data") + response = client.get("/api/digital-roadmap/v1/upcoming-changes/get-future-data") assert response.status_code == 200 diff --git a/app/v1/released/endpoints.py b/app/v1/released/endpoints.py index a82cbf8..73ad4b1 100644 --- a/app/v1/released/endpoints.py +++ b/app/v1/released/endpoints.py @@ -1,9 +1,11 @@ from typing import Optional -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.ext.asyncio import AsyncSession -from app.models import ReleaseModel -from app.services.naive_keyword_search import get_relevant_notes +from app.crud import get_paragraphs +from app.database import get_db +from app.models import TaggedParagraph v1_router = APIRouter() @@ -13,6 +15,21 @@ async def get_relevant( major: int = Query(..., description="Major version number"), minor: int = Query(..., description="Minor version number"), keywords: Optional[list[str]] = Query(None, description="List of keywords to search for"), + db: AsyncSession = Depends(get_db), ): - release = ReleaseModel(major=major, minor=minor) - return get_relevant_notes(release, keywords) + release_note_id = f"release_RHEL_{major}.{minor}" + + # This is obviously not enough + keyword = keywords[0] if keywords else "security" + + try: + paragraphs = await get_paragraphs(db, release_note_id, keyword) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + return [ + TaggedParagraph( + title=paragraph["section_id"], text=paragraph["raw_text"], tag="h3", relevant=paragraph["relevant"] + ) + for paragraph in paragraphs + ] diff --git a/requirements.txt b/requirements.txt index 143ca43..b7662c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,5 @@ fastapi uvicorn beautifulsoup4 pydantic +sqlalchemy +psycopg[binary]