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