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//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10use crate::error::ContentError;
11use crate::models::{SearchRequest, SearchResponse, SearchResult};
12use crate::repository::{ContentRepository, SearchRepository};
13use systemprompt_identifiers::CategoryId;
14
15const DEFAULT_SEARCH_LIMIT: i64 = 10;
16
17#[derive(Debug)]
18pub struct SearchService {
19    search_repo: SearchRepository,
20    content_repo: ContentRepository,
21}
22
23impl SearchService {
24    pub const fn new(search_repo: SearchRepository, content_repo: ContentRepository) -> Self {
25        Self {
26            search_repo,
27            content_repo,
28        }
29    }
30
31    pub async fn search(&self, request: &SearchRequest) -> Result<SearchResponse, ContentError> {
32        let limit = request.limit.unwrap_or(DEFAULT_SEARCH_LIMIT);
33
34        let results = if let Some(filters) = &request.filters {
35            if let Some(category_id) = &filters.category_id {
36                self.search_repo
37                    .search_by_category(category_id, limit)
38                    .await?
39            } else {
40                vec![]
41            }
42        } else {
43            let content_list = self.content_repo.list_all(limit, 0).await?;
44            content_list
45                .into_iter()
46                .map(Self::content_to_search_result)
47                .collect()
48        };
49
50        Ok(SearchResponse {
51            total: results.len(),
52            results,
53        })
54    }
55
56    pub async fn search_by_category(
57        &self,
58        category_id: &CategoryId,
59        limit: i64,
60    ) -> Result<Vec<SearchResult>, ContentError> {
61        Ok(self
62            .search_repo
63            .search_by_category(category_id, limit)
64            .await?)
65    }
66
67    fn content_to_search_result(content: crate::models::Content) -> SearchResult {
68        SearchResult {
69            id: content.id,
70            title: content.title,
71            slug: content.slug,
72            description: content.description,
73            image: content.image,
74            view_count: 0,
75            source_id: content.source_id,
76            category_id: content.category_id,
77        }
78    }
79}