Coverage for postrfp/ref/service/search_service.py: 89%
19 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
1"""
2DB helper functions for working with search
3"""
5from typing import Optional, Any
6from sqlalchemy.orm import Session
7from sqlalchemy import or_
8from sqlalchemy.sql import func
10from postrfp.shared.pager import Pager
11from postrfp.model import (
12 Content,
13 content_subjects_table,
14)
17def search_content_by_subject(
18 session: Session,
19 user_org_id: str,
20 subject_id: Optional[int] = None,
21 search_term: Optional[str] = None,
22 pager: Optional[Pager] = None,
23) -> tuple[int, list[Any]]:
24 """
25 Search for content by subject with permission filtering
27 Returns content items matching the search criteria that the user
28 has permission to view based on their organization.
30 Args:
31 session: Database session
32 user_org_id: Organization ID of the requesting user
33 subject_id: Optional subject ID to filter content by
34 name: Optional search term to match against title or content
35 pager: Pagination settings
37 Returns:
38 Tuple of (total_count, list_of_content_summary_rows)
39 """
40 # Build a query that selects only the columns we need
41 query = session.query(
42 Content.id,
43 Content.schema_id,
44 Content.title,
45 Content.date_created,
46 Content.date_updated,
47 ).filter(
48 or_(
49 # Content authored by user's org
50 Content.author_org_id == user_org_id
51 )
52 )
54 # Filter by subject if provided
55 if subject_id:
56 query = query.join(
57 content_subjects_table,
58 Content.id == content_subjects_table.c.content_id,
59 ).filter(content_subjects_table.c.subject_id == subject_id)
61 # Filter by search term if provided
62 if search_term:
63 # Use match search for content + title search for exact matches
64 query = query.filter(
65 or_(
66 # Match against full-text indexed content
67 func.match(Content.content_fts).against(search_term),
68 # Also match title for more precise results
69 Content.title.ilike(f"%{search_term}%"),
70 )
71 )
73 # Order by recently updated
74 query = query.order_by(Content.date_updated.desc())
76 # Get total count with same filters but without projection
77 count_query = query.with_entities(func.count()).order_by(None)
78 total_records = count_query.scalar()
80 # Apply pagination
81 if pager:
82 query = query.slice(pager.startfrom, pager.goto)
84 # Execute query to get rows
85 records = query.all()
87 return total_records, records