post_archiver/manager/
collection.rs1use rusqlite::params;
2
3use crate::{
4 error::Result,
5 manager::{binded::Binded, PostArchiverConnection},
6 query::FromQuery,
7 Collection, CollectionId, FileMetaId, PostId,
8};
9
10#[derive(Debug, Clone)]
12pub enum CollectionThumb {
13 Set(Option<FileMetaId>),
15 ByLatest,
17}
18
19#[derive(Debug, Clone, Default)]
23pub struct UpdateCollection {
24 pub name: Option<String>,
25 pub source: Option<Option<String>>,
26 pub thumb: Option<CollectionThumb>,
27}
28
29impl UpdateCollection {
30 pub fn name(mut self, name: String) -> Self {
32 self.name = Some(name);
33 self
34 }
35 pub fn source(mut self, source: Option<String>) -> Self {
37 self.source = Some(source);
38 self
39 }
40 pub fn thumb(mut self, thumb: Option<FileMetaId>) -> Self {
42 self.thumb = Some(CollectionThumb::Set(thumb));
43 self
44 }
45 pub fn thumb_by_latest(mut self) -> Self {
47 self.thumb = Some(CollectionThumb::ByLatest);
48 self
49 }
50}
51
52impl<'a, C: PostArchiverConnection> Binded<'a, CollectionId, C> {
56 pub fn value(&self) -> Result<Collection> {
58 let mut stmt = self
59 .conn()
60 .prepare_cached("SELECT * FROM collections WHERE id = ?")?;
61 Ok(stmt.query_row([self.id()], Collection::from_row)?)
62 }
63
64 pub fn delete(self) -> Result<()> {
68 self.conn()
69 .execute("DELETE FROM collections WHERE id = ?", [self.id()])?;
70 Ok(())
71 }
72
73 pub fn update(&self, update: UpdateCollection) -> Result<()> {
77 use rusqlite::types::ToSql;
78
79 let id = self.id();
80 let mut sets: Vec<&str> = Vec::new();
81 let mut params: Vec<&dyn ToSql> = Vec::new();
82
83 macro_rules! push {
84 ($field:expr, $col:expr) => {
85 if let Some(ref v) = $field {
86 sets.push($col);
87 params.push(v);
88 }
89 };
90 }
91
92 push!(update.name, "name = ?");
93 push!(update.source, "source = ?");
94
95 match &update.thumb {
96 Some(CollectionThumb::Set(v)) => {
97 sets.push("thumb = ?");
98 params.push(v);
99 }
100 Some(CollectionThumb::ByLatest) => {
101 sets.push("thumb = (SELECT posts.thumb FROM posts INNER JOIN collection_posts ON collection_posts.post = posts.id WHERE collection_posts.collection = ? AND posts.thumb IS NOT NULL ORDER BY posts.updated DESC LIMIT 1)");
102 params.push(&id);
103 }
104 None => {}
105 }
106
107 if sets.is_empty() {
108 return Ok(());
109 }
110
111 params.push(&id);
112
113 let sql = format!("UPDATE collections SET {} WHERE id = ?", sets.join(", "));
114 self.conn().execute(&sql, params.as_slice())?;
115 Ok(())
116 }
117}
118
119impl<'a, C: PostArchiverConnection> Binded<'a, CollectionId, C> {
123 pub fn list_posts(&self) -> Result<Vec<PostId>> {
125 let mut stmt = self
126 .conn()
127 .prepare_cached("SELECT post FROM collection_posts WHERE collection = ?")?;
128 let rows = stmt.query_map([self.id()], |row| row.get(0))?;
129 rows.collect::<std::result::Result<_, _>>()
130 .map_err(Into::into)
131 }
132
133 pub fn add_posts(&self, posts: &[PostId]) -> Result<()> {
135 let mut stmt = self.conn().prepare_cached(
136 "INSERT OR IGNORE INTO collection_posts (collection, post) VALUES (?, ?)",
137 )?;
138 for post in posts {
139 stmt.execute(params![self.id(), post])?;
140 }
141 Ok(())
142 }
143
144 pub fn remove_posts(&self, posts: &[PostId]) -> Result<()> {
146 let mut stmt = self
147 .conn()
148 .prepare_cached("DELETE FROM collection_posts WHERE collection = ? AND post = ?")?;
149 for post in posts {
150 stmt.execute(params![self.id(), post])?;
151 }
152 Ok(())
153 }
154}