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
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlmodel import Session
from src.core.events.database import get_db_session
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
router = APIRouter()
@ -14,6 +16,49 @@ def check_internal_cloud_key(request: Request):
):
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")
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)