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