diff --git a/src/content/post.rs b/src/content/post.rs index 24bfb8e..88a494d 100644 --- a/src/content/post.rs +++ b/src/content/post.rs @@ -93,10 +93,25 @@ pub struct UpdatePostData { pub blocks: Option>, } +/// Post with site and author dereferenced for convenience +#[derive(Serialize)] +pub struct PostWithAuthor { + pub author_display_name: Option, + pub author_username: String, + pub slug: String, + pub title: String, + pub description: Option, + pub tags: Vec, + pub blocks: Json>, + pub created_at: NaiveDateTime, + pub modified_at: Option, + pub deleted_at: Option, +} + #[async_trait] pub trait PostRepository { /// Get a post from its slug - async fn get_post_from_url(&self, site: &str, slug: &str) -> Result; + async fn get_post_from_url(&self, site: &str, slug: &str) -> Result; /// Create a new post async fn create_post( diff --git a/src/content/site.rs b/src/content/site.rs index 2b93be5..3504667 100644 --- a/src/content/site.rs +++ b/src/content/site.rs @@ -31,9 +31,10 @@ pub struct Site { /// More useful version of Site for showing to users #[derive(Serialize)] -pub struct APISite { +pub struct SiteWithAuthor { pub name: String, pub owner: String, + pub owner_display_name: Option, pub title: String, pub description: Option, pub created_at: NaiveDateTime, @@ -58,7 +59,7 @@ pub struct UpdateSiteData { #[async_trait] pub trait SiteRepository { /// Retrieve site info from site name/slug - async fn get_site_by_name(&self, name: &str) -> Result; + async fn get_site_by_name(&self, name: &str) -> Result; /// Create a new instance of a site and store it async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<(), Error>; diff --git a/src/database/post.rs b/src/database/post.rs index 44bd1cf..605c32b 100644 --- a/src/database/post.rs +++ b/src/database/post.rs @@ -1,10 +1,12 @@ use async_trait::async_trait; +use content::post::PostBlock; use serde_json::json; +use sqlx::types::Json; use uuid::Uuid; use crate::content::{ self, - post::{CreatePostData, Post, PostRepository, UpdatePostData}, + post::{CreatePostData, PostRepository, PostWithAuthor, UpdatePostData}, Error, }; @@ -12,18 +14,43 @@ use super::Database; #[async_trait] impl PostRepository for Database { - async fn get_post_from_url(&self, site: &str, slug: &str) -> Result { - let post_query = sqlx::query_as( - "SELECT pages.* FROM pages JOIN sites ON site = sites.id WHERE slug = $1 AND name = $2 AND pages.deleted_at IS NULL AND sites.deleted_at IS NULL") - .bind(slug) - .bind(site); - let post: Post = post_query - .fetch_one(&self.pool) - .await - .map_err(|e| match e { - sqlx::Error::RowNotFound => Error::NotFound, - _ => e.into(), - })?; + async fn get_post_from_url( + &self, + site: &str, + slug: &str, + ) -> Result { + 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>", + pages.created_at, + pages.modified_at, + pages.deleted_at + 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) } diff --git a/src/database/site.rs b/src/database/site.rs index fd2224f..d4d46f5 100644 --- a/src/database/site.rs +++ b/src/database/site.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use uuid::Uuid; use crate::content::{ - site::{APISite, CreateSiteData, SiteRepository, UpdateSiteData}, + site::{CreateSiteData, SiteRepository, SiteWithAuthor, UpdateSiteData}, Error, }; @@ -10,17 +10,30 @@ use super::Database; #[async_trait] impl SiteRepository for Database { - async fn get_site_by_name(&self, name: &str) -> Result { + async fn get_site_by_name(&self, name: &str) -> Result { let site = sqlx::query_as!( - APISite, - "SELECT sites.name, users.name as owner, title, description, sites.created_at FROM sites JOIN users ON sites.owner = users.id WHERE sites.name = $1 AND sites.deleted_at IS NULL", - name - ).fetch_one(&self.pool) - .await - .map_err(|e| match e { - sqlx::Error::RowNotFound => Error::NotFound, - _ => e.into(), - })?; + SiteWithAuthor, + r#"SELECT + sites.name, + users.name as owner, + COALESCE(users.display_name,users.name) as owner_display_name, + title, + description, + sites.created_at + FROM sites + JOIN + users ON sites.owner = users.id + WHERE + sites.name = $1 + AND sites.deleted_at IS NULL"#, + name + ) + .fetch_one(&self.pool) + .await + .map_err(|e| match e { + sqlx::Error::RowNotFound => Error::NotFound, + _ => e.into(), + })?; Ok(site) } @@ -50,18 +63,29 @@ impl SiteRepository for Database { options: UpdateSiteData, ) -> Result<(), Error> { let result = sqlx::query!( - "UPDATE sites SET name = COALESCE($1, name), title = COALESCE($2, title), description = COALESCE($3, description), modified_at = NOW() WHERE name = $4 AND owner = $5 AND deleted_at IS NULL", - options.name, - options.title, - options.description, - name, - owner, - ).execute(&self.pool).await.map_err(|err| match err { - sqlx::Error::Database(dberr) if dberr.constraint() == Some("sites_name_key") => { - Error::IdentifierNotAvailable - } - _ => err.into(), - })?; + r#"UPDATE sites SET + name = COALESCE($1, name), + title = COALESCE($2, title), + description = COALESCE($3, description), + modified_at = NOW() + WHERE + name = $4 + AND owner = $5 + AND deleted_at IS NULL"#, + options.name, + options.title, + options.description, + name, + owner, + ) + .execute(&self.pool) + .await + .map_err(|err| match err { + sqlx::Error::Database(dberr) if dberr.constraint() == Some("sites_name_key") => { + Error::IdentifierNotAvailable + } + _ => err.into(), + })?; if result.rows_affected() == 0 { return Err(Error::NotFound);