1use chrono::{DateTime, Utc};
2use rusqlite::params;
3
4use crate::{
5 error::Result,
6 manager::{binded::Binded, PostArchiverConnection},
7 query::FromQuery,
8 AuthorId, CollectionId, Comment, Content, FileMetaId, PlatformId, Post, PostId, TagId,
9};
10
11#[derive(Debug, Clone)]
13pub enum PostUpdated {
14 Set(DateTime<Utc>),
16 ByLatest(DateTime<Utc>),
18}
19
20#[derive(Debug, Clone, Default)]
25pub struct UpdatePost {
26 pub title: Option<String>,
27 pub source: Option<Option<String>>,
28 pub platform: Option<Option<PlatformId>>,
29 pub thumb: Option<Option<FileMetaId>>,
30 pub content: Option<Vec<Content>>,
31 pub comments: Option<Vec<Comment>>,
32 pub published: Option<DateTime<Utc>>,
33 pub updated: Option<PostUpdated>,
34}
35
36impl UpdatePost {
37 pub fn title(mut self, title: String) -> Self {
39 self.title = Some(title);
40 self
41 }
42 pub fn source(mut self, source: Option<String>) -> Self {
44 self.source = Some(source);
45 self
46 }
47 pub fn platform(mut self, platform: Option<PlatformId>) -> Self {
49 self.platform = Some(platform);
50 self
51 }
52 pub fn thumb(mut self, thumb: Option<FileMetaId>) -> Self {
54 self.thumb = Some(thumb);
55 self
56 }
57 pub fn content(mut self, content: Vec<Content>) -> Self {
59 self.content = Some(content);
60 self
61 }
62 pub fn comments(mut self, comments: Vec<Comment>) -> Self {
64 self.comments = Some(comments);
65 self
66 }
67 pub fn published(mut self, published: DateTime<Utc>) -> Self {
69 self.published = Some(published);
70 self
71 }
72 pub fn updated(mut self, updated: DateTime<Utc>) -> Self {
74 self.updated = Some(PostUpdated::Set(updated));
75 self
76 }
77 pub fn updated_by_latest(mut self, updated: DateTime<Utc>) -> Self {
79 self.updated = Some(PostUpdated::ByLatest(updated));
80 self
81 }
82}
83
84impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
88 pub fn value(&self) -> Result<Post> {
90 let mut stmt = self
91 .conn()
92 .prepare_cached("SELECT * FROM posts WHERE id = ?")?;
93 Ok(stmt.query_row([self.id()], Post::from_row)?)
94 }
95
96 pub fn delete(self) -> Result<()> {
101 let mut stmt = self
102 .conn()
103 .prepare_cached("DELETE FROM posts WHERE id = ?")?;
104 stmt.execute([self.id()])?;
105 Ok(())
106 }
107
108 pub fn update(&self, update: UpdatePost) -> Result<()> {
112 use rusqlite::types::ToSql;
113
114 let content_json = update.content.map(|c| serde_json::to_string(&c).unwrap());
116 let comments_json = update.comments.map(|c| serde_json::to_string(&c).unwrap());
117
118 let mut sets: Vec<&str> = Vec::new();
119 let mut params: Vec<&dyn ToSql> = Vec::new();
120
121 macro_rules! push {
122 ($field:expr, $col:expr) => {
123 if let Some(ref v) = $field {
124 sets.push($col);
125 params.push(v);
126 }
127 };
128 }
129
130 push!(update.title, "title = ?");
131 push!(update.source, "source = ?");
132 push!(update.platform, "platform = ?");
133 push!(update.thumb, "thumb = ?");
134 push!(content_json, "content = ?");
135 push!(comments_json, "comments = ?");
136 push!(update.published, "published = ?");
137
138 match &update.updated {
139 Some(PostUpdated::Set(t)) => {
140 sets.push("updated = ?");
141 params.push(t);
142 }
143 Some(PostUpdated::ByLatest(t)) => {
144 sets.push("updated = MAX(updated, ?)");
145 params.push(t);
146 }
147 None => {}
148 }
149
150 if sets.is_empty() {
151 return Ok(());
152 }
153
154 let id = self.id();
155 params.push(&id);
156
157 let sql = format!("UPDATE posts SET {} WHERE id = ?", sets.join(", "));
158 self.conn().execute(&sql, params.as_slice())?;
159 Ok(())
160 }
161
162 pub fn list_file_metas(&self) -> Result<Vec<FileMetaId>> {
164 let mut stmt = self
165 .conn()
166 .prepare_cached("SELECT id FROM file_metas WHERE post = ?")?;
167 let rows = stmt.query_map([self.id()], |row| row.get(0))?;
168 rows.collect::<std::result::Result<_, _>>()
169 .map_err(Into::into)
170 }
171}
172
173impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
177 pub fn list_authors(&self) -> Result<Vec<AuthorId>> {
179 let mut stmt = self
180 .conn()
181 .prepare_cached("SELECT author FROM author_posts WHERE post = ?")?;
182 let rows = stmt.query_map([self.id()], |row| row.get(0))?;
183 rows.collect::<std::result::Result<_, _>>()
184 .map_err(Into::into)
185 }
186
187 pub fn add_authors(&self, authors: &[AuthorId]) -> Result<()> {
190 let mut stmt = self
191 .conn()
192 .prepare_cached("INSERT OR IGNORE INTO author_posts (author, post) VALUES (?, ?)")?;
193 for author in authors {
194 stmt.execute(params![author, self.id()])?;
195 }
196 Ok(())
197 }
198
199 pub fn remove_authors(&self, authors: &[AuthorId]) -> Result<()> {
201 let mut stmt = self
202 .conn()
203 .prepare_cached("DELETE FROM author_posts WHERE post = ? AND author = ?")?;
204 for author in authors {
205 stmt.execute(params![self.id(), author])?;
206 }
207 Ok(())
208 }
209}
210
211impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
215 pub fn list_tags(&self) -> Result<Vec<TagId>> {
217 let mut stmt = self
218 .conn()
219 .prepare_cached("SELECT tag FROM post_tags WHERE post = ?")?;
220 let rows = stmt.query_map([self.id()], |row| row.get(0))?;
221 rows.collect::<std::result::Result<_, _>>()
222 .map_err(Into::into)
223 }
224
225 pub fn add_tags(&self, tags: &[TagId]) -> Result<()> {
228 let mut stmt = self
229 .conn()
230 .prepare_cached("INSERT OR IGNORE INTO post_tags (post, tag) VALUES (?, ?)")?;
231 for tag in tags {
232 stmt.execute(params![self.id(), tag])?;
233 }
234 Ok(())
235 }
236
237 pub fn remove_tags(&self, tags: &[TagId]) -> Result<()> {
239 let mut stmt = self
240 .conn()
241 .prepare_cached("DELETE FROM post_tags WHERE post = ? AND tag = ?")?;
242 for tag in tags {
243 stmt.execute(params![self.id(), tag])?;
244 }
245 Ok(())
246 }
247}
248
249impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
253 pub fn list_collections(&self) -> Result<Vec<CollectionId>> {
255 let mut stmt = self
256 .conn()
257 .prepare_cached("SELECT collection FROM collection_posts WHERE post = ?")?;
258 let rows = stmt.query_map([self.id()], |row| row.get(0))?;
259 rows.collect::<std::result::Result<_, _>>()
260 .map_err(Into::into)
261 }
262
263 pub fn add_collections(&self, collections: &[CollectionId]) -> Result<()> {
266 let mut stmt = self.conn().prepare_cached(
267 "INSERT OR IGNORE INTO collection_posts (collection, post) VALUES (?, ?)",
268 )?;
269 for collection in collections {
270 stmt.execute(params![collection, self.id()])?;
271 }
272 Ok(())
273 }
274
275 pub fn remove_collections(&self, collections: &[CollectionId]) -> Result<()> {
277 let mut stmt = self
278 .conn()
279 .prepare_cached("DELETE FROM collection_posts WHERE collection = ? AND post = ?")?;
280 for collection in collections {
281 stmt.execute(params![collection, self.id()])?;
282 }
283 Ok(())
284 }
285}