add author info to GETs
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Hamcha 2023-07-11 12:56:30 +02:00
parent 5f76de0a9a
commit f5c7570d9d
Signed by: hamcha
GPG Key ID: 1669C533B8CF6D89
4 changed files with 106 additions and 39 deletions

View File

@ -93,10 +93,25 @@ pub struct UpdatePostData {
pub blocks: Option<Vec<PostBlock>>, pub blocks: Option<Vec<PostBlock>>,
} }
/// Post with site and author dereferenced for convenience
#[derive(Serialize)]
pub struct PostWithAuthor {
pub author_display_name: Option<String>,
pub author_username: String,
pub slug: String,
pub title: String,
pub description: Option<String>,
pub tags: Vec<String>,
pub blocks: Json<Vec<PostBlock>>,
pub created_at: NaiveDateTime,
pub modified_at: Option<NaiveDateTime>,
pub deleted_at: Option<NaiveDateTime>,
}
#[async_trait] #[async_trait]
pub trait PostRepository { pub trait PostRepository {
/// Get a post from its slug /// Get a post from its slug
async fn get_post_from_url(&self, site: &str, slug: &str) -> Result<Post, Error>; async fn get_post_from_url(&self, site: &str, slug: &str) -> Result<PostWithAuthor, Error>;
/// Create a new post /// Create a new post
async fn create_post( async fn create_post(

View File

@ -31,9 +31,10 @@ pub struct Site {
/// More useful version of Site for showing to users /// More useful version of Site for showing to users
#[derive(Serialize)] #[derive(Serialize)]
pub struct APISite { pub struct SiteWithAuthor {
pub name: String, pub name: String,
pub owner: String, pub owner: String,
pub owner_display_name: Option<String>,
pub title: String, pub title: String,
pub description: Option<String>, pub description: Option<String>,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
@ -58,7 +59,7 @@ pub struct UpdateSiteData {
#[async_trait] #[async_trait]
pub trait SiteRepository { pub trait SiteRepository {
/// Retrieve site info from site name/slug /// Retrieve site info from site name/slug
async fn get_site_by_name(&self, name: &str) -> Result<APISite, Error>; async fn get_site_by_name(&self, name: &str) -> Result<SiteWithAuthor, Error>;
/// Create a new instance of a site and store it /// Create a new instance of a site and store it
async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<(), Error>; async fn create_site(&self, owner: &Uuid, options: CreateSiteData) -> Result<(), Error>;

View File

@ -1,10 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use content::post::PostBlock;
use serde_json::json; use serde_json::json;
use sqlx::types::Json;
use uuid::Uuid; use uuid::Uuid;
use crate::content::{ use crate::content::{
self, self,
post::{CreatePostData, Post, PostRepository, UpdatePostData}, post::{CreatePostData, PostRepository, PostWithAuthor, UpdatePostData},
Error, Error,
}; };
@ -12,18 +14,43 @@ use super::Database;
#[async_trait] #[async_trait]
impl PostRepository for Database { impl PostRepository for Database {
async fn get_post_from_url(&self, site: &str, slug: &str) -> Result<Post, content::Error> { async fn get_post_from_url(
let post_query = sqlx::query_as( &self,
"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") site: &str,
.bind(slug) slug: &str,
.bind(site); ) -> Result<PostWithAuthor, content::Error> {
let post: Post = post_query let post = sqlx::query_as!(
.fetch_one(&self.pool) PostWithAuthor,
.await r#"SELECT
.map_err(|e| match e { COALESCE(users.display_name,users.name) as author_display_name,
sqlx::Error::RowNotFound => Error::NotFound, users.name as author_username,
_ => e.into(), pages.slug,
})?; pages.title,
pages.description,
pages.tags,
pages.blocks as "blocks!: Json<Vec<PostBlock>>",
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) Ok(post)
} }

View File

@ -2,7 +2,7 @@ use async_trait::async_trait;
use uuid::Uuid; use uuid::Uuid;
use crate::content::{ use crate::content::{
site::{APISite, CreateSiteData, SiteRepository, UpdateSiteData}, site::{CreateSiteData, SiteRepository, SiteWithAuthor, UpdateSiteData},
Error, Error,
}; };
@ -10,17 +10,30 @@ use super::Database;
#[async_trait] #[async_trait]
impl SiteRepository for Database { impl SiteRepository for Database {
async fn get_site_by_name(&self, name: &str) -> Result<APISite, Error> { async fn get_site_by_name(&self, name: &str) -> Result<SiteWithAuthor, Error> {
let site = sqlx::query_as!( let site = sqlx::query_as!(
APISite, SiteWithAuthor,
"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", r#"SELECT
name sites.name,
).fetch_one(&self.pool) users.name as owner,
.await COALESCE(users.display_name,users.name) as owner_display_name,
.map_err(|e| match e { title,
sqlx::Error::RowNotFound => Error::NotFound, description,
_ => e.into(), 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) Ok(site)
} }
@ -50,18 +63,29 @@ impl SiteRepository for Database {
options: UpdateSiteData, options: UpdateSiteData,
) -> Result<(), Error> { ) -> Result<(), Error> {
let result = sqlx::query!( 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", r#"UPDATE sites SET
options.name, name = COALESCE($1, name),
options.title, title = COALESCE($2, title),
options.description, description = COALESCE($3, description),
name, modified_at = NOW()
owner, WHERE
).execute(&self.pool).await.map_err(|err| match err { name = $4
sqlx::Error::Database(dberr) if dberr.constraint() == Some("sites_name_key") => { AND owner = $5
Error::IdentifierNotAvailable AND deleted_at IS NULL"#,
} options.name,
_ => err.into(), 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 { if result.rows_affected() == 0 {
return Err(Error::NotFound); return Err(Error::NotFound);