Skip to content

Commit

Permalink
[Fix] wrong tags parse
Browse files Browse the repository at this point in the history
  • Loading branch information
mistricky committed Aug 31, 2024
1 parent 8106bfa commit 2e00add
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 37 deletions.
92 changes: 67 additions & 25 deletions api/src/dao/post_tags.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,69 @@
use crate::database::models::post_tags::{ActiveModel, Column, Entity, Model};
use sea_orm::{sea_query::OnConflict, ConnectionTrait, EntityTrait, InsertResult, QueryTrait, Set};
use crate::{
database::models::{
post_tags::{ActiveModel, Column, Entity},
tags::{Column as TagsColumn, Entity as Tags},
},
utils::helpers::{filter_record_not_insert_error, parse_load_many_result},
};
use sea_orm::{
sea_query::OnConflict, ColumnTrait, Condition, ConnectionTrait, EntityTrait, QueryFilter, Set,
};

pub async fn upsert_post_tags<T: ConnectionTrait>(
db: &T,
tag_ids: Vec<i32>,
post_id: i32,
) -> InsertResult<ActiveModel> {
Entity::insert_many(
tag_ids
.into_iter()
.map(|tag_id| ActiveModel {
tag_id: Set(tag_id),
post_id: Set(post_id),
..Default::default()
})
.collect::<Vec<ActiveModel>>(),
)
.on_conflict(
OnConflict::columns([Column::TagId, Column::PostId])
.do_nothing()
.to_owned(),
)
.exec(db)
.await
.unwrap()
pub async fn upsert_post_tags<T: ConnectionTrait>(db: &T, tags: &Vec<String>, post_id: i32) {
let tag_ids = Tags::find()
.filter(TagsColumn::Text.is_in(tags))
.all(db)
.await
.unwrap()
.into_iter()
.map(|tag| tag.id)
.collect::<Vec<i32>>();

filter_record_not_insert_error(
Entity::insert_many(
tag_ids
.into_iter()
.map(|tag_id| ActiveModel {
tag_id: Set(tag_id),
post_id: Set(post_id),
..Default::default()
})
.collect::<Vec<ActiveModel>>(),
)
.on_conflict(
OnConflict::columns([Column::TagId, Column::PostId])
.do_nothing()
.to_owned(),
)
.exec(db)
.await,
);
}

// When users delete some tag from a post, we should delete the corresponding row in the post_tags table.
// And if the deleted tag is not used by any other post, we should also delete the tag from the tags table.
pub async fn delete_tags_by_post_id<T: ConnectionTrait>(db: &T, tags: &Vec<String>, post_id: i32) {
let need_delete_tags = Entity::find()
.filter(Column::PostId.eq(post_id))
.find_with_related(Tags)
.filter(TagsColumn::Text.is_not_in(tags))
.all(db)
.await
.unwrap();

let need_delete_tag_ids = parse_load_many_result(need_delete_tags)
.into_iter()
.map(|tag| tag.id)
.collect::<Vec<i32>>();

// Delete tags in tags table, which has been deleted from disk
Entity::delete_many()
.filter(
Condition::all()
.add(Column::PostId.eq(post_id))
.add(Column::TagId.is_in(need_delete_tag_ids)),
)
.exec(db)
.await
.unwrap();
}
11 changes: 4 additions & 7 deletions api/src/dao/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use crate::{

use super::post_tags::upsert_post_tags;

async fn upsert_tags<T: ConnectionTrait>(db: &T, tags: Vec<String>) -> Vec<i32> {
async fn upsert_tags<T: ConnectionTrait>(db: &T, tags: &Vec<String>) -> Vec<i32> {
let tags_active_model_futures = tags.into_iter().map(|tag| async {
Entity::insert(ActiveModel {
text: Set(tag),
text: Set(tag.to_string()),
..Default::default()
})
.on_conflict(OnConflict::column(Column::Text).do_nothing().to_owned())
Expand All @@ -45,11 +45,8 @@ pub async fn upsert_tags_with_post_id(
) -> DBResult<(), TransactionError<DbErr>> {
db.transaction::<_, (), DbErr>(|txn| {
Box::pin(async move {
let tag_ids = upsert_tags(txn, tags).await;

if tag_ids.len() != 0 {
upsert_post_tags(txn, tag_ids, post_id).await;
}
upsert_tags(txn, &tags).await;
upsert_post_tags(txn, &tags, post_id).await;

Ok(())
})
Expand Down
11 changes: 6 additions & 5 deletions api/src/database/initialize.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
dao::{
post::{delete_posts_by_paths, get_post_by_path, upsert_post},
post_tags::delete_tags_by_post_id,
tag::upsert_tags_with_post_id,
},
utils::{
Expand All @@ -25,13 +26,13 @@ fn format_system_time_to_rfc2822(time: SystemTime) -> String {
}

async fn upsert_tags(db: &DatabaseConnection, tags: Option<Vec<String>>, post_id: i32) {
if tags.is_none() {
return;
}
let tags = tags.unwrap_or_default();

upsert_tags_with_post_id(db, tags.unwrap(), post_id)
upsert_tags_with_post_id(db, tags.clone(), post_id)
.await
.unwrap()
.unwrap();

delete_tags_by_post_id(db, &tags.clone(), post_id).await;
}

async fn upsert_posts(db: &DatabaseConnection, dir_entries: &Vec<DirEntry>) -> anyhow::Result<()> {
Expand Down
12 changes: 12 additions & 0 deletions api/src/utils/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use sea_orm::DbErr;

// SeaORM return Vec<(Model, Vec<Model>)>, but in most cases, we just need the second Model vector
pub fn parse_load_many_result<T: Clone, U: Clone>(result: Vec<(T, Vec<U>)>) -> Vec<U> {
if result.len() == 0 {
vec![]
Expand All @@ -7,3 +10,12 @@ pub fn parse_load_many_result<T: Clone, U: Clone>(result: Vec<(T, Vec<U>)>) -> V
inner
}
}

// SearORM will return DbErr::RecordNotInserted error when execute DoNothing on_conflict
// So we need to filter this error to avoid panic
pub fn filter_record_not_insert_error<T>(result: Result<T, DbErr>) {
match result {
Err(err) if err != DbErr::RecordNotInserted => panic!("{}", err),
_ => (),
}
}

0 comments on commit 2e00add

Please sign in to comment.