use chrono::{DateTime, Utc};
use rusqlite::params;
use crate::{
error::Result,
manager::{binded::Binded, PostArchiverConnection},
query::FromQuery,
AuthorId, CollectionId, Comment, Content, FileMetaId, PlatformId, Post, PostId, TagId,
};
#[derive(Debug, Clone)]
pub enum PostUpdated {
Set(DateTime<Utc>),
ByLatest(DateTime<Utc>),
}
#[derive(Debug, Clone, Default)]
pub struct UpdatePost {
pub title: Option<String>,
pub source: Option<Option<String>>,
pub platform: Option<Option<PlatformId>>,
pub thumb: Option<Option<FileMetaId>>,
pub content: Option<Vec<Content>>,
pub comments: Option<Vec<Comment>>,
pub published: Option<DateTime<Utc>>,
pub updated: Option<PostUpdated>,
}
impl UpdatePost {
pub fn title(mut self, title: String) -> Self {
self.title = Some(title);
self
}
pub fn source(mut self, source: Option<String>) -> Self {
self.source = Some(source);
self
}
pub fn platform(mut self, platform: Option<PlatformId>) -> Self {
self.platform = Some(platform);
self
}
pub fn thumb(mut self, thumb: Option<FileMetaId>) -> Self {
self.thumb = Some(thumb);
self
}
pub fn content(mut self, content: Vec<Content>) -> Self {
self.content = Some(content);
self
}
pub fn comments(mut self, comments: Vec<Comment>) -> Self {
self.comments = Some(comments);
self
}
pub fn published(mut self, published: DateTime<Utc>) -> Self {
self.published = Some(published);
self
}
pub fn updated(mut self, updated: DateTime<Utc>) -> Self {
self.updated = Some(PostUpdated::Set(updated));
self
}
pub fn updated_by_latest(mut self, updated: DateTime<Utc>) -> Self {
self.updated = Some(PostUpdated::ByLatest(updated));
self
}
}
impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
pub fn value(&self) -> Result<Post> {
let mut stmt = self
.conn()
.prepare_cached("SELECT * FROM posts WHERE id = ?")?;
Ok(stmt.query_row([self.id()], Post::from_row)?)
}
pub fn delete(self) -> Result<()> {
let mut stmt = self
.conn()
.prepare_cached("DELETE FROM posts WHERE id = ?")?;
stmt.execute([self.id()])?;
Ok(())
}
pub fn update(&self, update: UpdatePost) -> Result<()> {
use rusqlite::types::ToSql;
let content_json = update.content.map(|c| serde_json::to_string(&c).unwrap());
let comments_json = update.comments.map(|c| serde_json::to_string(&c).unwrap());
let mut sets: Vec<&str> = Vec::new();
let mut params: Vec<&dyn ToSql> = Vec::new();
macro_rules! push {
($field:expr, $col:expr) => {
if let Some(ref v) = $field {
sets.push($col);
params.push(v);
}
};
}
push!(update.title, "title = ?");
push!(update.source, "source = ?");
push!(update.platform, "platform = ?");
push!(update.thumb, "thumb = ?");
push!(content_json, "content = ?");
push!(comments_json, "comments = ?");
push!(update.published, "published = ?");
match &update.updated {
Some(PostUpdated::Set(t)) => {
sets.push("updated = ?");
params.push(t);
}
Some(PostUpdated::ByLatest(t)) => {
sets.push("updated = MAX(updated, ?)");
params.push(t);
}
None => {}
}
if sets.is_empty() {
return Ok(());
}
let id = self.id();
params.push(&id);
let sql = format!("UPDATE posts SET {} WHERE id = ?", sets.join(", "));
self.conn().execute(&sql, params.as_slice())?;
Ok(())
}
pub fn list_file_metas(&self) -> Result<Vec<FileMetaId>> {
let mut stmt = self
.conn()
.prepare_cached("SELECT id FROM file_metas WHERE post = ?")?;
let rows = stmt.query_map([self.id()], |row| row.get(0))?;
rows.collect::<std::result::Result<_, _>>()
.map_err(Into::into)
}
}
impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
pub fn list_authors(&self) -> Result<Vec<AuthorId>> {
let mut stmt = self
.conn()
.prepare_cached("SELECT author FROM author_posts WHERE post = ?")?;
let rows = stmt.query_map([self.id()], |row| row.get(0))?;
rows.collect::<std::result::Result<_, _>>()
.map_err(Into::into)
}
pub fn add_authors(&self, authors: &[AuthorId]) -> Result<()> {
let mut stmt = self
.conn()
.prepare_cached("INSERT OR IGNORE INTO author_posts (author, post) VALUES (?, ?)")?;
for author in authors {
stmt.execute(params![author, self.id()])?;
}
Ok(())
}
pub fn remove_authors(&self, authors: &[AuthorId]) -> Result<()> {
let mut stmt = self
.conn()
.prepare_cached("DELETE FROM author_posts WHERE post = ? AND author = ?")?;
for author in authors {
stmt.execute(params![self.id(), author])?;
}
Ok(())
}
}
impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
pub fn list_tags(&self) -> Result<Vec<TagId>> {
let mut stmt = self
.conn()
.prepare_cached("SELECT tag FROM post_tags WHERE post = ?")?;
let rows = stmt.query_map([self.id()], |row| row.get(0))?;
rows.collect::<std::result::Result<_, _>>()
.map_err(Into::into)
}
pub fn add_tags(&self, tags: &[TagId]) -> Result<()> {
let mut stmt = self
.conn()
.prepare_cached("INSERT OR IGNORE INTO post_tags (post, tag) VALUES (?, ?)")?;
for tag in tags {
stmt.execute(params![self.id(), tag])?;
}
Ok(())
}
pub fn remove_tags(&self, tags: &[TagId]) -> Result<()> {
let mut stmt = self
.conn()
.prepare_cached("DELETE FROM post_tags WHERE post = ? AND tag = ?")?;
for tag in tags {
stmt.execute(params![self.id(), tag])?;
}
Ok(())
}
}
impl<'a, C: PostArchiverConnection> Binded<'a, PostId, C> {
pub fn list_collections(&self) -> Result<Vec<CollectionId>> {
let mut stmt = self
.conn()
.prepare_cached("SELECT collection FROM collection_posts WHERE post = ?")?;
let rows = stmt.query_map([self.id()], |row| row.get(0))?;
rows.collect::<std::result::Result<_, _>>()
.map_err(Into::into)
}
pub fn add_collections(&self, collections: &[CollectionId]) -> Result<()> {
let mut stmt = self.conn().prepare_cached(
"INSERT OR IGNORE INTO collection_posts (collection, post) VALUES (?, ?)",
)?;
for collection in collections {
stmt.execute(params![collection, self.id()])?;
}
Ok(())
}
pub fn remove_collections(&self, collections: &[CollectionId]) -> Result<()> {
let mut stmt = self
.conn()
.prepare_cached("DELETE FROM collection_posts WHERE collection = ? AND post = ?")?;
for collection in collections {
stmt.execute(params![collection, self.id()])?;
}
Ok(())
}
}