Skip to main content

systemprompt_content/services/
content_provider.rs

1use async_trait::async_trait;
2use systemprompt_database::DbPool;
3use systemprompt_identifiers::LocaleCode;
4use systemprompt_traits::content::{ContentFilter, ContentItem, ContentProvider, ContentSummary};
5
6use crate::error::ContentError;
7use crate::repository::{ContentRepository, SearchRepository};
8
9#[derive(Debug)]
10pub struct DefaultContentProvider {
11    repo: ContentRepository,
12    search_repo: SearchRepository,
13}
14
15impl DefaultContentProvider {
16    pub fn new(db: &DbPool) -> Result<Self, ContentError> {
17        Ok(Self {
18            repo: ContentRepository::new(db)?,
19            search_repo: SearchRepository::new(db)?,
20        })
21    }
22}
23
24#[async_trait]
25impl ContentProvider for DefaultContentProvider {
26    type Error = ContentError;
27
28    async fn get_content(&self, id: &str) -> Result<Option<ContentItem>, Self::Error> {
29        let content_id = systemprompt_identifiers::ContentId::new(id);
30        let content = self.repo.get_by_id(&content_id).await?;
31
32        Ok(content.map(|c| ContentItem {
33            id: c.id,
34            slug: c.slug,
35            title: c.title,
36            description: c.description,
37            body: c.body,
38            author: c.author,
39            published_at: c.published_at,
40            keywords: c.keywords,
41            kind: c.kind,
42            image: c.image,
43            source_id: c.source_id,
44            category_id: c.category_id.map(|id| id.to_string()),
45        }))
46    }
47
48    async fn get_content_by_slug(&self, slug: &str) -> Result<Option<ContentItem>, Self::Error> {
49        let content = self.repo.get_by_slug(slug, &LocaleCode::new("en")).await?;
50
51        Ok(content.map(|c| ContentItem {
52            id: c.id,
53            slug: c.slug,
54            title: c.title,
55            description: c.description,
56            body: c.body,
57            author: c.author,
58            published_at: c.published_at,
59            keywords: c.keywords,
60            kind: c.kind,
61            image: c.image,
62            source_id: c.source_id,
63            category_id: c.category_id.map(|id| id.to_string()),
64        }))
65    }
66
67    async fn get_content_by_source_and_slug(
68        &self,
69        source_id: &systemprompt_identifiers::SourceId,
70        slug: &str,
71    ) -> Result<Option<ContentItem>, Self::Error> {
72        let content = self
73            .repo
74            .get_by_source_and_slug(source_id, slug, &LocaleCode::new("en"))
75            .await?;
76
77        Ok(content.map(|c| ContentItem {
78            id: c.id,
79            slug: c.slug,
80            title: c.title,
81            description: c.description,
82            body: c.body,
83            author: c.author,
84            published_at: c.published_at,
85            keywords: c.keywords,
86            kind: c.kind,
87            image: c.image,
88            source_id: c.source_id,
89            category_id: c.category_id.map(|id| id.to_string()),
90        }))
91    }
92
93    async fn list_content(
94        &self,
95        filter: ContentFilter,
96    ) -> Result<Vec<ContentSummary>, Self::Error> {
97        let limit = filter.limit.unwrap_or(100);
98        let offset = filter.offset.unwrap_or(0);
99
100        let contents = if let Some(source_id) = &filter.source_id {
101            self.repo
102                .list_by_source(source_id, &LocaleCode::new("en"))
103                .await?
104        } else {
105            self.repo.list(limit, offset).await?
106        };
107
108        Ok(contents
109            .into_iter()
110            .map(|c| ContentSummary {
111                id: c.id,
112                slug: c.slug,
113                title: c.title,
114                description: c.description,
115                published_at: c.published_at,
116                kind: c.kind,
117                source_id: c.source_id,
118            })
119            .collect())
120    }
121
122    async fn search(
123        &self,
124        query: &str,
125        limit: Option<i64>,
126    ) -> Result<Vec<ContentSummary>, Self::Error> {
127        let limit = limit.unwrap_or(50);
128        let results = self.search_repo.search_by_keyword(query, limit).await?;
129
130        Ok(results
131            .into_iter()
132            .map(|r| ContentSummary {
133                id: r.id,
134                slug: r.slug,
135                title: r.title,
136                description: r.description,
137                published_at: chrono::Utc::now(),
138                kind: String::new(),
139                source_id: r.source_id,
140            })
141            .collect())
142    }
143}