Skip to main content

systemprompt_content/services/search/
mod.rs

1//! Content search service.
2//!
3//! [`SearchService`] resolves a [`SearchRequest`] into a [`SearchResponse`],
4//! dispatching category-filtered queries to the search repository and falling
5//! back to a recency-ordered content listing when no filter is supplied.
6
7use crate::error::ContentError;
8use crate::models::{SearchRequest, SearchResponse, SearchResult};
9use crate::repository::{ContentRepository, SearchRepository};
10use systemprompt_database::DbPool;
11use systemprompt_identifiers::CategoryId;
12
13const DEFAULT_SEARCH_LIMIT: i64 = 10;
14
15#[derive(Debug)]
16pub struct SearchService {
17    search_repo: SearchRepository,
18    content_repo: ContentRepository,
19}
20
21impl SearchService {
22    pub fn new(db: &DbPool) -> Result<Self, ContentError> {
23        Ok(Self {
24            search_repo: SearchRepository::new(db)?,
25            content_repo: ContentRepository::new(db)?,
26        })
27    }
28
29    pub async fn search(&self, request: &SearchRequest) -> Result<SearchResponse, ContentError> {
30        let limit = request.limit.unwrap_or(DEFAULT_SEARCH_LIMIT);
31
32        let results = if let Some(filters) = &request.filters {
33            if let Some(category_id) = &filters.category_id {
34                self.search_repo
35                    .search_by_category(category_id, limit)
36                    .await?
37            } else {
38                vec![]
39            }
40        } else {
41            let content_list = self.content_repo.list_all(limit, 0).await?;
42            content_list
43                .into_iter()
44                .map(Self::content_to_search_result)
45                .collect()
46        };
47
48        Ok(SearchResponse {
49            total: results.len(),
50            results,
51        })
52    }
53
54    pub async fn search_by_category(
55        &self,
56        category_id: &CategoryId,
57        limit: i64,
58    ) -> Result<Vec<SearchResult>, ContentError> {
59        Ok(self
60            .search_repo
61            .search_by_category(category_id, limit)
62            .await?)
63    }
64
65    fn content_to_search_result(content: crate::models::Content) -> SearchResult {
66        SearchResult {
67            id: content.id,
68            title: content.title,
69            slug: content.slug,
70            description: content.description,
71            image: content.image,
72            view_count: 0,
73            source_id: content.source_id,
74            category_id: content.category_id,
75        }
76    }
77}