Skip to content

Commit

Permalink
Merge pull request #3 from tikitko/ref/splitPostsQuery
Browse files Browse the repository at this point in the history
ref: post and tags in different queries
  • Loading branch information
tikitko authored Jul 10, 2023
2 parents efdf125 + ea2b59b commit 26b0114
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 48 deletions.
26 changes: 9 additions & 17 deletions blog-server-api/src/entities/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,6 @@ pub struct Post {

impl Into<Post> for ServicePost {
fn into(self) -> Post {
// TODO: refactor next
let tags: Vec<Tag> = self
.tags
.split(";")
.filter_map(|t| {
let tag_parts: Vec<&str> = t.split(",").collect();
if tag_parts.len() == 2 {
Some(Tag {
title: tag_parts[1].to_owned(),
slug: tag_parts[0].to_owned(),
})
} else {
None
}
})
.collect();
Post {
title: self.base.title,
slug: self.base.slug,
Expand All @@ -43,7 +27,15 @@ impl Into<Post> for ServicePost {
first_name: self.author_first_name,
last_name: self.author_last_name,
},
tags,
tags: self
.tags
.into_iter()
.map(|v| Tag {
title: v.title,
//TODO: Change to ID
slug: v.id.to_string(),
})
.collect(),
}
}
}
137 changes: 107 additions & 30 deletions blog-server-services/src/impls/rbatis_post_service.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
use crate::traits::post_service::{BasePost, Post, PostService};
use crate::traits::post_service::{BasePost, Post, PostService, Tag};
use rbatis::rbatis::RBatis;
use screw_components::dyn_result::{DError, DResult};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub fn create_rbatis_post_service(rb: RBatis) -> Box<dyn PostService> {
Box::new(RbatisPostService { rb })
}

impl_insert!(BasePost {}, "post");

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct TagDto {
post_id: i64,
id: i64,
title: String,
}

impl Into<Tag> for TagDto {
fn into(self) -> Tag {
Tag {
id: self.id,
title: self.title,
}
}
}

impl Post {
#[py_sql(
"
Expand All @@ -34,14 +53,10 @@ impl Post {
post.*, \
author.slug AS author_slug, \
author.first_name AS author_first_name, \
author.last_name AS author_last_name, \
string_agg(concat_ws(',', tag.slug, tag.title), ';') as tags \
author.last_name AS author_last_name \
FROM post \
JOIN author ON post.author_id = author.id \
LEFT JOIN post_tag ON post_tag.post_id = post.id \
LEFT JOIN tag ON tag.id = post_tag.tag_id \
WHERE post.id = #{id} \
GROUP BY post.id, author.slug, author.first_name, author.last_name \
LIMIT 1 \
"
)]
Expand All @@ -54,42 +69,50 @@ impl Post {
post.*, \
author.slug AS author_slug, \
author.first_name AS author_first_name, \
author.last_name AS author_last_name, \
string_agg(concat_ws(',', tag.slug, tag.title), ';') as tags \
author.last_name AS author_last_name \
FROM post \
JOIN author ON post.author_id = author.id \
LEFT JOIN post_tag ON post_tag.post_id = post.id \
LEFT JOIN tag ON tag.id = post_tag.tag_id \
WHERE post.slug = #{slug} \
GROUP BY post.id, author.slug, author.first_name, author.last_name \
LIMIT 1 \
"
)]
async fn select_by_slug(rb: &RBatis, slug: &String) -> rbatis::Result<Option<Post>> {
impled!()
}

#[py_sql(
"
SELECT \
post.*, \
author.slug AS author_slug, \
author.first_name AS author_first_name, \
author.last_name AS author_last_name, \
string_agg(concat_ws(',', tag.slug, tag.title), ';') as tags \
author.last_name AS author_last_name \
FROM post \
JOIN author ON post.author_id = author.id \
LEFT JOIN post_tag ON post_tag.post_id = post.id \
LEFT JOIN tag ON tag.id = post_tag.tag_id \
GROUP BY post.id, author.slug, author.first_name, author.last_name \
LIMIT #{limit} \
OFFSET #{offset} \
"
)]
async fn select_all_with_limit_and_offset(
rb: &RBatis,
limit: &i64,
offset: &i64,
) -> rbatis::Result<Vec<Post>> {
async fn select_posts(rb: &RBatis, limit: &i64, offset: &i64) -> rbatis::Result<Vec<Post>> {
impled!()
}

#[py_sql(
"
SELECT \
tag.id, \
tag.title, \
post_tag.post_id \
FROM post_tag \
JOIN tag ON tag.id = post_tag.tag_id \
WHERE \
post_tag.post_id IN (
trim ',': for _,item in post_ids:
#{item},
) \
"
)]
async fn select_tags_by_posts(rb: &RBatis, post_ids: Vec<i64>) -> rbatis::Result<Vec<TagDto>> {
impled!()
}
#[py_sql(
Expand All @@ -98,14 +121,10 @@ impl Post {
post.*, \
author.slug AS author_slug, \
author.first_name AS author_first_name, \
author.last_name AS author_last_name, \
string_agg(concat_ws(',', tag.slug, tag.title), ';') as tags \
author.last_name AS author_last_name \
FROM post \
JOIN author ON post.author_id = author.id \
LEFT JOIN post_tag ON post_tag.post_id = post.id \
LEFT JOIN tag ON tag.id = post_tag.tag_id \
WHERE post.title ILIKE '%' || #{query} || '%' OR post.summary ILIKE '%' || #{query} || '%' OR post.content ILIKE '%' || #{query} || '%' \
GROUP BY post.id, author.slug, author.first_name, author.last_name \
LIMIT #{limit} \
OFFSET #{offset} \
"
Expand All @@ -118,12 +137,61 @@ impl Post {
) -> rbatis::Result<Vec<Post>> {
impled!()
}

fn apply_tags(&mut self, tags: Vec<Tag>) {
self.tags = tags;
}
}

struct RbatisPostService {
rb: RBatis,
}

impl RbatisPostService {
async fn saturate_with_tags(&self, post_option: Option<Post>) -> DResult<Option<Post>> {
match post_option {
None => Ok(None),
Some(mut post) => {
let post_tags = Post::select_tags_by_posts(&self.rb, vec![post.id])
.await?
.into_iter()
.map(|tag| tag.into())
.collect();
post.apply_tags(post_tags);
Ok(Some(post))
}
}
}

async fn saturate_posts_with_tags(&self, mut posts: Vec<Post>) -> DResult<Vec<Post>> {
if posts.is_empty() {
return Ok(posts);
}

let post_ids = posts.iter().map(|post| post.id).collect();

let mut grouped_tags: HashMap<i64, Vec<Tag>> =
Post::select_tags_by_posts(&self.rb, post_ids)
.await?
.into_iter()
.fold(HashMap::new(), |mut map, dto| {
let key = dto.post_id;
let tag = dto.into();
map.entry(key).or_insert_with(Vec::new).push(tag);
map
});

for post in posts.iter_mut() {
match grouped_tags.remove(&post.id) {
Some(tags) => post.apply_tags(tags),
None => {}
}
}

Ok(posts)
}
}

#[async_trait]
impl PostService for RbatisPostService {
async fn posts_count_by_query(&self, query: &String) -> DResult<i64> {
Expand All @@ -135,20 +203,29 @@ impl PostService for RbatisPostService {
offset: &i64,
limit: &i64,
) -> DResult<Vec<Post>> {
Ok(Post::select_all_by_query_with_limit_and_offset(&self.rb, query, limit, offset).await?)
let posts =
Post::select_all_by_query_with_limit_and_offset(&self.rb, query, limit, offset).await?;
RbatisPostService::saturate_posts_with_tags(&self, posts).await
}

async fn posts_count(&self) -> DResult<i64> {
Ok(Post::count(&self.rb).await?)
}
async fn posts(&self, offset: &i64, limit: &i64) -> DResult<Vec<Post>> {
Ok(Post::select_all_with_limit_and_offset(&self.rb, limit, offset).await?)
let posts = Post::select_posts(&self.rb, limit, offset).await?;
RbatisPostService::saturate_posts_with_tags(&self, posts).await
}

async fn post_by_id(&self, id: &i64) -> DResult<Option<Post>> {
Ok(Post::select_by_id(&self.rb, id).await?)
let post_option = Post::select_by_id(&self.rb, id).await?;
RbatisPostService::saturate_with_tags(&self, post_option).await
}

async fn post_by_slug(&self, slug: &String) -> DResult<Option<Post>> {
Ok(Post::select_by_slug(&self.rb, slug).await?)
let post_option = Post::select_by_slug(&self.rb, slug).await?;
RbatisPostService::saturate_with_tags(&self, post_option).await
}

async fn create_post(&self, post: &BasePost) -> DResult<i64> {
let insert_result = BasePost::insert(&mut self.rb.clone(), post).await?;
let last_insert_id = insert_result
Expand Down
10 changes: 9 additions & 1 deletion blog-server-services/src/traits/post_service.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use screw_components::dyn_result::DResult;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct Tag {
pub id: i64,
pub title: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct BasePost {
Expand All @@ -20,7 +27,8 @@ pub struct Post {
pub author_slug: String,
pub author_first_name: Option<String>,
pub author_last_name: Option<String>,
pub tags: String,
#[serde(default)]
pub tags: Vec<Tag>,
#[serde(flatten)]
pub base: BasePost,
}
Expand Down

0 comments on commit 26b0114

Please sign in to comment.