systemprompt_content/models/
content.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use sqlx::FromRow;
5use systemprompt_identifiers::{CategoryId, ContentId, SourceId, TagId};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8#[serde(rename_all = "lowercase")]
9pub enum ContentKind {
10 #[default]
11 Article,
12 Guide,
13 Tutorial,
14}
15
16impl ContentKind {
17 pub const fn as_str(&self) -> &'static str {
18 match self {
19 Self::Article => "article",
20 Self::Guide => "guide",
21 Self::Tutorial => "tutorial",
22 }
23 }
24}
25
26impl std::fmt::Display for ContentKind {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(f, "{}", self.as_str())
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
33pub struct Content {
34 pub id: ContentId,
35 pub slug: String,
36 pub title: String,
37 pub description: String,
38 pub body: String,
39 pub author: String,
40 pub published_at: DateTime<Utc>,
41 pub keywords: String,
42 pub kind: String,
43 pub image: Option<String>,
44 pub category_id: Option<CategoryId>,
45 pub source_id: SourceId,
46 pub version_hash: String,
47 pub public: bool,
48 #[serde(default)]
49 pub links: JsonValue,
50 pub updated_at: DateTime<Utc>,
51}
52
53impl Content {
54 pub fn links_metadata(&self) -> Result<Vec<ContentLinkMetadata>, serde_json::Error> {
55 serde_json::from_value(self.links.clone())
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ContentSummary {
61 pub id: ContentId,
62 pub slug: String,
63 pub title: String,
64 pub description: String,
65 pub published_at: DateTime<Utc>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ContentMetadata {
70 pub title: String,
71 #[serde(default)]
72 pub description: String,
73 #[serde(default)]
74 pub author: String,
75 pub published_at: String,
76 pub slug: String,
77 #[serde(default)]
78 pub keywords: String,
79 pub kind: String,
80 #[serde(default)]
81 pub image: Option<String>,
82 #[serde(default)]
83 pub category: Option<String>,
84 #[serde(default)]
85 pub tags: Vec<String>,
86 #[serde(default)]
87 pub links: Vec<ContentLinkMetadata>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct ContentLinkMetadata {
92 pub title: String,
93 pub url: String,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
97pub struct Tag {
98 pub id: TagId,
99 pub name: String,
100 pub slug: String,
101 pub created_at: Option<DateTime<Utc>>,
102 pub updated_at: Option<DateTime<Utc>>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct IngestionReport {
107 pub files_found: usize,
108 pub files_processed: usize,
109 pub errors: Vec<String>,
110 #[serde(default)]
111 pub warnings: Vec<String>,
112 #[serde(default, skip_serializing_if = "Vec::is_empty")]
113 pub would_create: Vec<String>,
114 #[serde(default, skip_serializing_if = "Vec::is_empty")]
115 pub would_update: Vec<String>,
116 #[serde(default)]
117 pub unchanged_count: usize,
118}
119
120impl IngestionReport {
121 pub const fn new() -> Self {
122 Self {
123 files_found: 0,
124 files_processed: 0,
125 errors: Vec::new(),
126 warnings: Vec::new(),
127 would_create: Vec::new(),
128 would_update: Vec::new(),
129 unchanged_count: 0,
130 }
131 }
132
133 pub fn is_success(&self) -> bool {
134 self.errors.is_empty()
135 }
136}
137
138impl Default for IngestionReport {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144#[derive(Debug, Clone, Copy, Default)]
145pub struct IngestionOptions {
146 pub override_existing: bool,
147 pub recursive: bool,
148 pub dry_run: bool,
149}
150
151impl IngestionOptions {
152 pub const fn with_override(mut self, override_existing: bool) -> Self {
153 self.override_existing = override_existing;
154 self
155 }
156
157 pub const fn with_recursive(mut self, recursive: bool) -> Self {
158 self.recursive = recursive;
159 self
160 }
161
162 pub const fn with_dry_run(mut self, dry_run: bool) -> Self {
163 self.dry_run = dry_run;
164 self
165 }
166}
167
168#[derive(Debug, Clone)]
169pub struct IngestionSource<'a> {
170 pub source_id: &'a SourceId,
171 pub category_id: &'a CategoryId,
172}
173
174impl<'a> IngestionSource<'a> {
175 pub const fn new(source_id: &'a SourceId, category_id: &'a CategoryId) -> Self {
176 Self {
177 source_id,
178 category_id,
179 }
180 }
181}