Skip to main content

post_archiver/query/
author.rs

1//! Author query builder and related point-query helpers.
2
3use rusqlite::OptionalExtension;
4
5use crate::{
6    manager::{PostArchiverConnection, PostArchiverManager},
7    Alias, Author, AuthorId, PlatformId,
8};
9
10use super::{
11    filter::{DateFilter, IdFilter, TextFilter},
12    sortable::impl_sortable,
13    BaseFilter, FromQuery, Query, Queryer, RawSql,
14};
15
16/// Fluent query builder for authors.  Obtained via [`PostArchiverManager::authors()`].
17///
18/// # Available filter fields
19/// - `ids`: filter by a set of [`AuthorId`] values.
20/// - `name`: `LIKE` fuzzy match on the author name.
21/// - `updated`: date-range filter on the last-updated timestamp.
22#[derive(Debug)]
23pub struct AuthorQuery<'a, C> {
24    queryer: Queryer<'a, C>,
25    pub ids: IdFilter<AuthorId>,
26    pub name: TextFilter,
27    pub updated: DateFilter,
28}
29
30impl<'a, C: PostArchiverConnection> AuthorQuery<'a, C> {
31    pub fn new(manager: &'a PostArchiverManager<C>) -> Self {
32        AuthorQuery {
33            queryer: Queryer::new(manager),
34            ids: IdFilter::new("id"),
35            name: TextFilter::new("name"),
36            updated: DateFilter::new("updated"),
37        }
38    }
39}
40
41impl_sortable!(AuthorQuery(AuthorSort) {
42    Id: "id",
43    Name: "name",
44    Updated: "updated"
45});
46
47impl<C: PostArchiverConnection> BaseFilter for AuthorQuery<'_, C> {
48    type Based = Author;
49
50    fn update_sql<T: FromQuery<Based = Self::Based>>(&self, mut sql: RawSql<T>) -> RawSql<T> {
51        sql = self.ids.build_sql(sql);
52        sql = self.name.build_sql(sql);
53        sql = self.updated.build_sql(sql);
54
55        sql
56    }
57
58    fn queryer(&self) -> &Queryer<'_, impl PostArchiverConnection> {
59        &self.queryer
60    }
61}
62
63impl<C: PostArchiverConnection> Query for AuthorQuery<'_, C> {
64    type Wrapper<U> = Vec<U>;
65    type Based = Author;
66
67    fn query_with_context<T: FromQuery<Based = Self::Based>>(
68        self,
69        sql: RawSql<T>,
70    ) -> crate::error::Result<Self::Wrapper<T>> {
71        let sql = self.update_sql(sql);
72        let (sql, params) = sql.build_sql();
73        self.queryer.fetch(&sql, params)
74    }
75}
76
77// ── Point-query helpers on PostArchiverManager ────────────────────────────────
78
79impl<C: PostArchiverConnection> PostArchiverManager<C> {
80    /// Entry point for the author query builder.
81    pub fn authors(&self) -> AuthorQuery<'_, C> {
82        AuthorQuery::new(self)
83    }
84
85    /// Fetch a single author by primary key. Returns `None` if not found.
86    pub fn get_author(&self, id: AuthorId) -> crate::error::Result<Option<Author>> {
87        let mut stmt = self
88            .conn()
89            .prepare_cached("SELECT * FROM authors WHERE id = ?")?;
90        Ok(stmt.query_row([id], Author::from_row).optional()?)
91    }
92
93    /// Find an author ID by alias (`source` + `platform`).
94    pub fn find_author_by_alias(
95        &self,
96        source: &str,
97        platform: PlatformId,
98    ) -> crate::error::Result<Option<AuthorId>> {
99        let mut stmt = self.conn().prepare_cached(
100            "SELECT target FROM author_aliases WHERE platform = ? AND source = ?",
101        )?;
102        Ok(stmt
103            .query_row(rusqlite::params![platform, source], |row| row.get(0))
104            .optional()?)
105    }
106
107    /// Fetch all aliases belonging to the given author.
108    pub fn list_author_aliases(&self, author: AuthorId) -> crate::error::Result<Vec<Alias>> {
109        let mut stmt = self
110            .conn()
111            .prepare_cached("SELECT * FROM author_aliases WHERE target = ?")?;
112        let rows = stmt.query_map([author], Alias::from_row)?;
113        rows.collect::<Result<_, _>>().map_err(Into::into)
114    }
115}