diff --git a/README.md b/README.md index a861782..159af5b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Python +## Python° > Production-ready, open-source FastAPI application with PostgreSQL and blazing-fast full-text search. diff --git a/app/api/prospects/__init__.py b/app/api/prospects/__init__.py index 74874ea..24c986d 100644 --- a/app/api/prospects/__init__.py +++ b/app/api/prospects/__init__.py @@ -1,3 +1,4 @@ """Prospect Routes""" from .prospects import router as prospects_router +from .flagged import router as flagged_router diff --git a/app/api/prospects/flagged.py b/app/api/prospects/flagged.py new file mode 100644 index 0000000..b52dec3 --- /dev/null +++ b/app/api/prospects/flagged.py @@ -0,0 +1,69 @@ +from app import __version__ +import os +from app.utils.make_meta import make_meta +from fastapi import APIRouter, Query, Path, Body, HTTPException +from app.utils.db import get_db_connection + +router = APIRouter() +base_url = os.getenv("BASE_URL", "http://localhost:8000") + + +# Refactored GET /prospects endpoint to return paginated, filtered, and ordered results +@router.get("/prospects/flagged") +def get_prospects( + page: int = Query(1, ge=1, description="Page number (1-based)"), + limit: int = Query(50, ge=1, le=500, description="Records per page (default 50, max 500)"), + search: str = Query(None, description="Search term for first or last name (case-insensitive, partial match)") +) -> dict: + """Return flagged prospects, filtered by search if provided.""" + meta = make_meta("success", "Flagged prospects") + conn_gen = get_db_connection() + conn = next(conn_gen) + cur = conn.cursor() + offset = (page - 1) * limit + try: + where_clauses = ["hide IS NOT TRUE", "flag IS TRUE"] + params = [] + if search: + where_clauses.append("(LOWER(first_name) LIKE %s OR LOWER(last_name) LIKE %s)") + search_param = f"%{search.lower()}%" + params.extend([search_param, search_param]) + where_sql = " AND ".join(where_clauses) + + # Count query + count_query = f'SELECT COUNT(*) FROM prospects WHERE {where_sql};' + cur.execute(count_query, params) + count_row = cur.fetchone() if cur.description is not None else None + total = count_row[0] if count_row is not None else 0 + + # Data query + data_query = f''' + SELECT * FROM prospects + WHERE {where_sql} + ORDER BY first_name ASC + OFFSET %s LIMIT %s; + ''' + cur.execute(data_query, params + [offset, limit]) + if cur.description is not None: + columns = [desc[0] for desc in cur.description] + rows = cur.fetchall() + data = [dict(zip(columns, row)) for row in rows] + else: + data = [] + except Exception as e: + data = [] + total = 0 + meta = make_meta("error", f"Failed to read prospects: {str(e)}") + finally: + cur.close() + conn.close() + return { + "meta": meta, + "pagination": { + "page": page, + "limit": limit, + "total": total, + "pages": (total // limit) + (1 if total % limit else 0) + }, + "data": data, + } \ No newline at end of file diff --git a/app/api/prospects/prospects.py b/app/api/prospects/prospects.py index 1e6e62e..1727174 100644 --- a/app/api/prospects/prospects.py +++ b/app/api/prospects/prospects.py @@ -1,4 +1,3 @@ - from app import __version__ import os from app.utils.make_meta import make_meta @@ -42,7 +41,7 @@ def get_prospects( data_query = f''' SELECT * FROM prospects WHERE {where_sql} - ORDER BY COALESCE(flag, FALSE) DESC, first_name ASC + ORDER BY first_name ASC OFFSET %s LIMIT %s; ''' cur.execute(data_query, params + [offset, limit]) diff --git a/app/api/root.py b/app/api/root.py index 8967396..fcc0e1f 100644 --- a/app/api/root.py +++ b/app/api/root.py @@ -23,7 +23,13 @@ def root() -> dict: {"name": "docs", "url": f"{base_url}/docs"}, {"name": "resend", "url": f"{base_url}/resend"}, {"name": "health", "url": f"{base_url}/health"}, - {"name": "prospects", "url": f"{base_url}/prospects"}, {"name": "llm", "url": f"{base_url}/llm"}, + { + "name": "prospects", + "endpoints": [ + {"name": "list", "url": f"{base_url}/prospects"}, + {"name": "flagged", "url": f"{base_url}/prospects/flagged"}, + ] + } ] return {"meta": meta, "data": endpoints} diff --git a/app/api/routes.py b/app/api/routes.py index 13afaad..a60b505 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -9,11 +9,13 @@ from app.api.resend.resend import router as resend_router from app.api.llm.llm import router as llm_router from app.api.prospects.prospects import router as prospects_router +from app.api.prospects.flagged import router as flagged_router from app.api.llm.llm import router as gemini_router router.include_router(root_router) router.include_router(resend_router) router.include_router(health_router) router.include_router(llm_router) +router.include_router(flagged_router) router.include_router(prospects_router) router.include_router(gemini_router) diff --git a/tests/test_resend.py b/tests/test_resend.py index 394e975..1e13ee6 100644 --- a/tests/test_resend.py +++ b/tests/test_resend.py @@ -13,7 +13,7 @@ def test_resend_post_email(monkeypatch): payload = { "to": 'listingslab@gmail.com', - "subject": "Python pytest", + "subject": "pytest", "html": "Python tests have run" } response = client.post("/resend", json=payload)