Skip to main content

post_archiver/manager/
tag.rs

1use crate::{
2    error::Result,
3    manager::{binded::Binded, PostArchiverConnection},
4    query::FromQuery,
5    PlatformId, PostId, Tag, TagId,
6};
7
8/// Builder for updating a tag's fields.
9///
10/// Fields left as `None` are not modified.
11/// For nullable columns (platform), use `Some(None)` to clear the value.
12#[derive(Debug, Clone, Default)]
13pub struct UpdateTag {
14    pub name: Option<String>,
15    pub platform: Option<Option<PlatformId>>,
16}
17
18impl UpdateTag {
19    /// Set the tag's name.
20    pub fn name(mut self, name: String) -> Self {
21        self.name = Some(name);
22        self
23    }
24    /// Set or clear the tag's platform.
25    pub fn platform(mut self, platform: Option<PlatformId>) -> Self {
26        self.platform = Some(platform);
27        self
28    }
29}
30
31//=============================================================
32// FindTag trait (kept for importer compatibility)
33//=============================================================
34pub trait FindTag {
35    fn name(&self) -> &str;
36    fn platform(&self) -> Option<PlatformId> {
37        None
38    }
39}
40
41impl FindTag for &str {
42    fn name(&self) -> &str {
43        self
44    }
45}
46
47impl FindTag for (&str, PlatformId) {
48    fn name(&self) -> &str {
49        self.0
50    }
51    fn platform(&self) -> Option<PlatformId> {
52        Some(self.1)
53    }
54}
55
56impl FindTag for (&str, Option<PlatformId>) {
57    fn name(&self) -> &str {
58        self.0
59    }
60    fn platform(&self) -> Option<PlatformId> {
61        self.1
62    }
63}
64
65//=============================================================
66// Update / Delete
67//=============================================================
68impl<'a, C: PostArchiverConnection> Binded<'a, TagId, C> {
69    /// Get this tag's current data from the database.
70    pub fn value(&self) -> Result<Tag> {
71        let mut stmt = self
72            .conn()
73            .prepare_cached("SELECT * FROM tags WHERE id = ?")?;
74        Ok(stmt.query_row([self.id()], Tag::from_row)?)
75    }
76
77    /// Remove this tag from the archive.
78    ///
79    /// This will also remove all post-tag relationships, but will not delete the posts themselves.
80    pub fn delete(self) -> Result<()> {
81        let mut stmt = self
82            .conn()
83            .prepare_cached("DELETE FROM tags WHERE id = ?")?;
84        stmt.execute([self.id()])?;
85        Ok(())
86    }
87
88    /// Apply a batch of field updates to this tag in a single SQL statement.
89    ///
90    /// Only fields set on `update` (i.e. `Some(...)`) are written to the database.
91    pub fn update(&self, update: UpdateTag) -> Result<()> {
92        use rusqlite::types::ToSql;
93
94        let mut sets: Vec<&str> = Vec::new();
95        let mut params: Vec<&dyn ToSql> = Vec::new();
96
97        macro_rules! push {
98            ($field:expr, $col:expr) => {
99                if let Some(ref v) = $field {
100                    sets.push($col);
101                    params.push(v);
102                }
103            };
104        }
105
106        push!(update.name, "name = ?");
107        push!(update.platform, "platform = ?");
108
109        if sets.is_empty() {
110            return Ok(());
111        }
112
113        let id = self.id();
114        params.push(&id);
115
116        let sql = format!("UPDATE tags SET {} WHERE id = ?", sets.join(", "));
117        self.conn().execute(&sql, params.as_slice())?;
118        Ok(())
119    }
120}
121
122//=============================================================
123// Relations: Posts
124//=============================================================
125impl<'a, C: PostArchiverConnection> Binded<'a, TagId, C> {
126    /// List all post IDs associated with this tag.
127    pub fn list_posts(&self) -> Result<Vec<PostId>> {
128        let mut stmt = self
129            .conn()
130            .prepare_cached("SELECT post FROM post_tags WHERE tag = ?")?;
131        let rows = stmt.query_map([self.id()], |row| row.get(0))?;
132        rows.collect::<std::result::Result<_, _>>()
133            .map_err(Into::into)
134    }
135}