1use chrono::{DateTime, Utc};
2use rusqlite::params;
3
4use crate::{
5 error::Result,
6 manager::{binded::Binded, PostArchiverConnection},
7 query::FromQuery,
8 Alias, Author, AuthorId, FileMetaId, PlatformId, PostId,
9};
10
11#[derive(Debug, Clone)]
13pub enum AuthorThumb {
14 Set(Option<FileMetaId>),
16 ByLatest,
18}
19
20#[derive(Debug, Clone)]
22pub enum AuthorUpdated {
23 Set(DateTime<Utc>),
25 ByLatest,
27}
28
29#[derive(Debug, Clone, Default)]
33pub struct UpdateAuthor {
34 pub name: Option<String>,
35 pub thumb: Option<AuthorThumb>,
36 pub updated: Option<AuthorUpdated>,
37}
38
39impl UpdateAuthor {
40 pub fn name(mut self, name: String) -> Self {
42 self.name = Some(name);
43 self
44 }
45 pub fn thumb(mut self, thumb: Option<FileMetaId>) -> Self {
47 self.thumb = Some(AuthorThumb::Set(thumb));
48 self
49 }
50 pub fn thumb_by_latest(mut self) -> Self {
52 self.thumb = Some(AuthorThumb::ByLatest);
53 self
54 }
55 pub fn updated(mut self, updated: DateTime<Utc>) -> Self {
57 self.updated = Some(AuthorUpdated::Set(updated));
58 self
59 }
60 pub fn updated_by_latest(mut self) -> Self {
62 self.updated = Some(AuthorUpdated::ByLatest);
63 self
64 }
65}
66
67impl<'a, C: PostArchiverConnection> Binded<'a, AuthorId, C> {
71 pub fn value(&self) -> Result<Author> {
73 let mut stmt = self
74 .conn()
75 .prepare_cached("SELECT * FROM authors WHERE id = ?")?;
76 Ok(stmt.query_row([self.id()], Author::from_row)?)
77 }
78
79 pub fn delete(self) -> Result<()> {
83 self.conn()
84 .execute("DELETE FROM authors WHERE id = ?", [self.id()])?;
85 Ok(())
86 }
87
88 pub fn update(&self, update: UpdateAuthor) -> Result<()> {
92 use rusqlite::types::ToSql;
93
94 let id = self.id();
95 let mut sets: Vec<&str> = Vec::new();
96 let mut params: Vec<&dyn ToSql> = Vec::new();
97
98 macro_rules! push {
99 ($field:expr, $col:expr) => {
100 if let Some(ref v) = $field {
101 sets.push($col);
102 params.push(v);
103 }
104 };
105 }
106
107 push!(update.name, "name = ?");
108
109 match &update.thumb {
110 Some(AuthorThumb::Set(v)) => {
111 sets.push("thumb = ?");
112 params.push(v);
113 }
114 Some(AuthorThumb::ByLatest) => {
115 sets.push("thumb = (SELECT thumb FROM posts WHERE id IN (SELECT post FROM author_posts WHERE author = ?) AND thumb IS NOT NULL ORDER BY updated DESC LIMIT 1)");
116 params.push(&id);
117 }
118 None => {}
119 }
120
121 match &update.updated {
122 Some(AuthorUpdated::Set(v)) => {
123 sets.push("updated = ?");
124 params.push(v);
125 }
126 Some(AuthorUpdated::ByLatest) => {
127 sets.push("updated = (SELECT updated FROM posts WHERE id IN (SELECT post FROM author_posts WHERE author = ?) ORDER BY updated DESC LIMIT 1)");
128 params.push(&id);
129 }
130 None => {}
131 }
132
133 if sets.is_empty() {
134 return Ok(());
135 }
136
137 params.push(&id);
138
139 let sql = format!("UPDATE authors SET {} WHERE id = ?", sets.join(", "));
140 self.conn().execute(&sql, params.as_slice())?;
141 Ok(())
142 }
143}
144
145impl<'a, C: PostArchiverConnection> Binded<'a, AuthorId, C> {
149 pub fn list_aliases(&self) -> Result<Vec<Alias>> {
151 let mut stmt = self
152 .conn()
153 .prepare_cached("SELECT * FROM author_aliases WHERE target = ?")?;
154 let rows = stmt.query_map([self.id()], Alias::from_row)?;
155 rows.collect::<std::result::Result<_, _>>()
156 .map_err(Into::into)
157 }
158
159 pub fn add_aliases(&self, aliases: Vec<(String, PlatformId, Option<String>)>) -> Result<()> {
164 let mut stmt = self.conn().prepare_cached(
165 "INSERT OR REPLACE INTO author_aliases (target, source, platform, link) VALUES (?, ?, ?, ?)",
166 )?;
167 for (source, platform, link) in aliases {
168 stmt.execute(params![self.id(), source, platform, link])?;
169 }
170 Ok(())
171 }
172
173 pub fn remove_aliases(&self, aliases: &[(String, PlatformId)]) -> Result<()> {
178 let mut stmt = self.conn().prepare_cached(
179 "DELETE FROM author_aliases WHERE target = ? AND source = ? AND platform = ?",
180 )?;
181 for (source, platform) in aliases {
182 stmt.execute(params![self.id(), source, platform])?;
183 }
184 Ok(())
185 }
186
187 pub fn set_alias_name(&self, alias: &(String, PlatformId), name: String) -> Result<()> {
189 let mut stmt = self.conn().prepare_cached(
190 "UPDATE author_aliases SET source = ? WHERE target = ? AND source = ? AND platform = ?",
191 )?;
192 stmt.execute(params![name, self.id(), alias.0, alias.1])?;
193 Ok(())
194 }
195
196 pub fn set_alias_platform(
198 &self,
199 alias: &(String, PlatformId),
200 platform: PlatformId,
201 ) -> Result<()> {
202 let mut stmt = self.conn().prepare_cached(
203 "UPDATE author_aliases SET platform = ? WHERE target = ? AND source = ? AND platform = ?",
204 )?;
205 stmt.execute(params![platform, self.id(), alias.0, alias.1])?;
206 Ok(())
207 }
208
209 pub fn set_alias_link(&self, alias: &(String, PlatformId), link: Option<String>) -> Result<()> {
211 let mut stmt = self.conn().prepare_cached(
212 "UPDATE author_aliases SET link = ? WHERE target = ? AND source = ? AND platform = ?",
213 )?;
214 stmt.execute(params![link, self.id(), alias.0, alias.1])?;
215 Ok(())
216 }
217}
218
219impl<'a, C: PostArchiverConnection> Binded<'a, AuthorId, C> {
223 pub fn list_posts(&self) -> Result<Vec<PostId>> {
225 let mut stmt = self
226 .conn()
227 .prepare_cached("SELECT post FROM author_posts WHERE author = ?")?;
228 let rows = stmt.query_map([self.id()], |row| row.get(0))?;
229 rows.collect::<std::result::Result<_, _>>()
230 .map_err(Into::into)
231 }
232}
233
234pub trait FindAlias {
238 fn source(&self) -> &str;
239 fn platform(&self) -> PlatformId;
240}
241
242impl FindAlias for (&str, PlatformId) {
243 fn source(&self) -> &str {
244 self.0
245 }
246 fn platform(&self) -> PlatformId {
247 self.1
248 }
249}
250
251#[cfg(feature = "importer")]
252impl FindAlias for crate::importer::UnsyncAlias {
253 fn source(&self) -> &str {
254 &self.source
255 }
256 fn platform(&self) -> PlatformId {
257 self.platform
258 }
259}