systemprompt_content/services/
content_provider.rs1use 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}