Skip to main content

systemprompt_content/services/
content_provider.rs

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