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
|
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(
|
||||||
|
|
|
||||||
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