feat: add explore API endpoints for organizations and courses

- Implemented new API routes for exploring organizations and their courses.
- Added endpoints for retrieving organizations, searching organizations, and fetching courses associated with a specific organization.
- Introduced functionality to get details of a specific course for exploration.
This commit is contained in:
swve 2024-12-18 16:53:31 +01:00
parent 5006b1abad
commit d8f77aec4c
2 changed files with 210 additions and 0 deletions

View file

@ -1,8 +1,10 @@
import os import os
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request
from sqlmodel import Session from sqlmodel import Session
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from src.db.organization_config import OrganizationConfigBase from src.db.organization_config import OrganizationConfigBase
from src.services.explore.explore import get_course_for_explore, get_courses_for_an_org_explore, get_org_for_explore, get_orgs_for_explore, search_orgs_for_explore
from src.services.orgs.orgs import update_org_with_config_no_auth from src.services.orgs.orgs import update_org_with_config_no_auth
router = APIRouter() router = APIRouter()
@ -14,6 +16,49 @@ def check_internal_cloud_key(request: Request):
): ):
raise HTTPException(status_code=403, detail="Unauthorized") raise HTTPException(status_code=403, detail="Unauthorized")
@router.get("/explore/orgs")
async def api_get_orgs_for_explore(
request: Request,
page: int = 1,
limit: int = 10,
label: str = "",
salt: str = "",
db_session: Session = Depends(get_db_session),
):
return await get_orgs_for_explore(request, db_session, page, limit, label, salt)
@router.get("/explore/orgs/search")
async def api_search_orgs_for_explore(
request: Request,
search_query: str,
label: Optional[str] = None,
db_session: Session = Depends(get_db_session),
):
return await search_orgs_for_explore(request, db_session, search_query, label)
@router.get("/explore/orgs/{org_uuid}/courses")
async def api_get_courses_for_explore(
request: Request,
org_uuid: str,
db_session: Session = Depends(get_db_session),
):
return await get_courses_for_an_org_explore(request, db_session, org_uuid)
@router.get("/explore/courses/{course_id}")
async def api_get_course_for_explore(
request: Request,
course_id: str,
db_session: Session = Depends(get_db_session),
):
return await get_course_for_explore(request, course_id, db_session)
@router.get("/explore/orgs/{org_slug}")
async def api_get_org_for_explore(
request: Request,
org_slug: str,
db_session: Session = Depends(get_db_session),
):
return await get_org_for_explore(request, org_slug, db_session)
@router.put("/update_org_config") @router.put("/update_org_config")
async def update_org_Config( async def update_org_Config(

View file

@ -0,0 +1,165 @@
from typing import Optional
from fastapi import HTTPException, Request
from sqlmodel import Session, select
from sqlalchemy import text
from src.db.courses.courses import Course, CourseRead
from src.db.organizations import Organization, OrganizationRead
def _get_sort_expression(salt: str):
"""Helper function to create consistent sort expression"""
if not salt:
return Organization.name
# Create a deterministic ordering using md5(salt + id)
return text(
f"md5('{salt}' || id)"
)
async def get_orgs_for_explore(
request: Request,
db_session: Session,
page: int = 1,
limit: int = 10,
label: str = "",
salt: str = "",
) -> list[OrganizationRead]:
statement = (
select(Organization)
.where(
Organization.explore == True,
)
)
# Add label filter if provided
if label:
statement = statement.where(Organization.label == label) #type: ignore
# Add deterministic ordering based on salt
statement = statement.order_by(_get_sort_expression(salt))
# Add pagination
statement = (
statement
.offset((page - 1) * limit)
.limit(limit)
)
result = db_session.exec(statement)
orgs = result.all()
return [OrganizationRead.model_validate(org) for org in orgs]
async def get_courses_for_an_org_explore(
request: Request,
db_session: Session,
org_uuid: str,
) -> list[CourseRead]:
statement = select(Organization).where(Organization.org_uuid == org_uuid)
result = db_session.exec(statement)
org = result.first()
if not org:
raise HTTPException(
status_code=404,
detail="Organization not found",
)
statement = select(Course).where(Course.org_id == org.id, Course.public == True)
result = db_session.exec(statement)
courses = result.all()
courses_list = []
for course in courses:
courses_list.append(course)
return courses_list
async def get_course_for_explore(
request: Request,
course_id: str,
db_session: Session,
) -> CourseRead:
statement = select(Course).where(Course.id == course_id)
result = db_session.exec(statement)
course = result.first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
return CourseRead.model_validate(course)
async def search_orgs_for_explore(
request: Request,
db_session: Session,
search_query: str,
label: Optional[str] = None,
page: int = 1,
limit: int = 10,
salt: str = "",
) -> list[OrganizationRead]:
# Create a combined search vector
search_terms = search_query.split()
search_conditions = []
for term in search_terms:
term_pattern = f"%{term}%"
search_conditions.append(
(Organization.name.ilike(term_pattern)) | #type: ignore
(Organization.about.ilike(term_pattern)) | #type: ignore
(Organization.description.ilike(term_pattern)) | #type: ignore
(Organization.label.ilike(term_pattern)) #type: ignore
)
statement = (
select(Organization)
.where(Organization.explore == True)
)
if label and label != "all":
statement = statement.where(Organization.label == label) #type: ignore
if search_conditions:
statement = statement.where(*search_conditions)
# Add deterministic ordering based on salt
statement = statement.order_by(_get_sort_expression(salt))
# Add pagination
statement = (
statement
.offset((page - 1) * limit)
.limit(limit)
)
result = db_session.exec(statement)
orgs = result.all()
return [OrganizationRead.model_validate(org) for org in orgs]
async def get_org_for_explore(
request: Request,
org_slug: str,
db_session: Session,
) -> OrganizationRead:
statement = select(Organization).where(Organization.slug == org_slug)
result = db_session.exec(statement)
org = result.first()
if not org:
raise HTTPException(
status_code=404,
detail="Organization not found",
)
return OrganizationRead.model_validate(org)