mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
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:
parent
5006b1abad
commit
d8f77aec4c
2 changed files with 210 additions and 0 deletions
|
|
@ -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(
|
||||
|
|
|
|||
165
apps/api/src/services/explore/explore.py
Normal file
165
apps/api/src/services/explore/explore.py
Normal 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)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue