mabel/src/database/post.rs

199 lines
5.8 KiB
Rust

use async_trait::async_trait;
use chrono::NaiveDateTime;
use content::post::PostBlock;
use serde_json::json;
use sqlx::types::Json;
use uuid::Uuid;
use crate::{
content::{
self,
post::{CreatePostData, PostRepository, PostWithAuthor, UpdatePostData},
Error,
},
cursor::CursorList,
};
use super::Database;
#[async_trait]
impl PostRepository for Database {
async fn list_posts_in_collection(
&self,
site: &Uuid,
collection: &Uuid,
from: Option<NaiveDateTime>,
limit: i64,
) -> Result<CursorList<PostWithAuthor, NaiveDateTime>, Error> {
let posts = sqlx::query_as!(
PostWithAuthor,
r#"SELECT
COALESCE(users.display_name,users.name) as author_display_name,
users.name as author_username,
pages.slug,
pages.title,
pages.description,
pages.tags,
pages.blocks as "blocks!: Json<Vec<PostBlock>>",
pages.created_at,
pages.modified_at,
pages.published,
pages.collections
FROM pages
JOIN
users ON author = users.id
WHERE
pages.site = $1
AND $2 = ANY(collections)
AND pages.deleted_at IS NULL
AND published = true
AND pages.created_at <= $3
ORDER BY created_at DESC
LIMIT $4
"#,
site,
collection,
from.unwrap_or_else(|| NaiveDateTime::MAX),
limit
)
.fetch_all(&self.pool)
.await?;
Ok(CursorList::new(posts, limit as usize))
}
async fn get_post_from_url(
&self,
site: &str,
slug: &str,
) -> Result<PostWithAuthor, content::Error> {
let post = sqlx::query_as!(
PostWithAuthor,
r#"SELECT
COALESCE(users.display_name,users.name) as author_display_name,
users.name as author_username,
pages.slug,
pages.title,
pages.description,
pages.tags,
pages.blocks as "blocks!: Json<Vec<PostBlock>>",
pages.created_at,
pages.modified_at,
pages.published,
pages.collections
FROM pages
JOIN
sites ON site = sites.id
JOIN
users ON author = users.id
WHERE
slug = $1
AND sites.name = $2
AND pages.deleted_at IS NULL
AND sites.deleted_at IS NULL"#,
slug,
site
)
.fetch_one(&self.pool)
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => Error::NotFound,
_ => e.into(),
})?;
Ok(post)
}
async fn create_post(
&self,
owner: &Uuid,
site: &Uuid,
data: CreatePostData,
) -> Result<(), Error> {
sqlx::query!(
"INSERT INTO pages (id, author, site, slug, title, description, tags, blocks, collections, published)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
Uuid::now_v7(),
owner,
site,
data.slug,
data.title,
data.description.unwrap_or_else(|| "".to_string()),
&data.tags,
json!(data.blocks),
&data.collections,
data.published
).execute(&self.pool).await.map_err(|err| match err {
sqlx::Error::Database(dberr) if dberr.constraint() == Some("pages_site_slug_unique") => {
Error::IdentifierNotAvailable
}
_ => err.into(),
})?;
Ok(())
}
async fn update_post(
&self,
owner: &Uuid,
site: &Uuid,
slug: &str,
data: UpdatePostData,
) -> Result<(), Error> {
let result = sqlx::query!(
"UPDATE pages SET title = COALESCE($1, title), description = COALESCE($2, description), tags = COALESCE($3, tags), blocks = COALESCE($4, blocks), collections = COALESCE($8, collections), published = COALESCE($9, published) WHERE author = $5 AND site = $6 AND slug = $7 AND deleted_at IS NULL",
data.title,
data.description,
data.tags.as_deref(),
data.blocks.map(|x| json!(x)),
owner,
site,
slug,
data.collections.as_deref(),
data.published
)
.execute(&self.pool).await.map_err(|err| match err {
sqlx::Error::Database(dberr) if dberr.constraint() == Some("pages_site_slug_unique") => {
Error::IdentifierNotAvailable
}
_ => err.into(),
})?;
if result.rows_affected() == 0 {
return Err(Error::NotFound);
}
Ok(())
}
async fn delete_post(
&self,
owner: &Uuid,
site: &Uuid,
slug: &str,
soft_delete: bool,
) -> Result<(), Error> {
let result = if soft_delete {
sqlx::query!(
"UPDATE pages SET deleted_at = NOW() WHERE author = $1 AND site = $2 AND slug = $3 AND deleted_at IS NULL",
owner,
site,
slug
).execute(&self.pool).await?
} else {
sqlx::query!(
"DELETE FROM pages WHERE author = $1 AND site = $2 AND slug = $3",
owner,
site,
slug
)
.execute(&self.pool)
.await?
};
if result.rows_affected() == 0 {
return Err(Error::NotFound);
}
Ok(())
}
}