diff --git a/.sqlx/query-304da1f7fec4fcd69c2e0e0bbb24edb0bce2e988c6fea1eb856b7625b4d1f16f.json b/.sqlx/query-304da1f7fec4fcd69c2e0e0bbb24edb0bce2e988c6fea1eb856b7625b4d1f16f.json deleted file mode 100644 index df054795..00000000 --- a/.sqlx/query-304da1f7fec4fcd69c2e0e0bbb24edb0bce2e988c6fea1eb856b7625b4d1f16f.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n avr.comment_id,\n avr.reaction_type,\n au.uuid AS user_uuid,\n au.name AS user_name\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE view_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "comment_id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "reaction_type", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "user_uuid", - "type_info": "Uuid" - }, - { - "ordinal": 3, - "name": "user_name", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "304da1f7fec4fcd69c2e0e0bbb24edb0bce2e988c6fea1eb856b7625b4d1f16f" -} diff --git a/.sqlx/query-74eaa9170bfd355ec23bfaf793e091e2b1433ddb59ec9f5fa811a914c38d71a7.json b/.sqlx/query-74eaa9170bfd355ec23bfaf793e091e2b1433ddb59ec9f5fa811a914c38d71a7.json new file mode 100644 index 00000000..68e456ef --- /dev/null +++ b/.sqlx/query-74eaa9170bfd355ec23bfaf793e091e2b1433ddb59ec9f5fa811a914c38d71a7.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n avr.reaction_type,\n MIN(avr.created_at) AS reaction_type_creation_at,\n ARRAY_AGG((au.uuid, au.name)) AS \"users!: Vec\"\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE comment_id = $1\n GROUP BY reaction_type\n ORDER BY reaction_type_creation_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "reaction_type", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "reaction_type_creation_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "users!: Vec", + "type_info": "RecordArray" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + null, + null + ] + }, + "hash": "74eaa9170bfd355ec23bfaf793e091e2b1433ddb59ec9f5fa811a914c38d71a7" +} diff --git a/.sqlx/query-9d6cdb956061b5cbd2198ab5215508c2ea58a462e8decbc4804e45335c3770f8.json b/.sqlx/query-9d6cdb956061b5cbd2198ab5215508c2ea58a462e8decbc4804e45335c3770f8.json new file mode 100644 index 00000000..659c4391 --- /dev/null +++ b/.sqlx/query-9d6cdb956061b5cbd2198ab5215508c2ea58a462e8decbc4804e45335c3770f8.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n avr.comment_id,\n avr.reaction_type,\n MIN(avr.created_at) AS reaction_type_creation_at,\n ARRAY_AGG((au.uuid, au.name)) AS \"users!: Vec\"\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE view_id = $1\n GROUP BY comment_id, reaction_type\n ORDER BY reaction_type_creation_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "comment_id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "reaction_type", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "reaction_type_creation_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "users!: Vec", + "type_info": "RecordArray" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + false, + null, + null + ] + }, + "hash": "9d6cdb956061b5cbd2198ab5215508c2ea58a462e8decbc4804e45335c3770f8" +} diff --git a/.sqlx/query-f9a80c40a2dea06b391a065c946472ff8e1a8e63425155e88b472b91c1e24f3a.json b/.sqlx/query-f9a80c40a2dea06b391a065c946472ff8e1a8e63425155e88b472b91c1e24f3a.json deleted file mode 100644 index 83d4156f..00000000 --- a/.sqlx/query-f9a80c40a2dea06b391a065c946472ff8e1a8e63425155e88b472b91c1e24f3a.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n avr.comment_id,\n avr.reaction_type,\n au.uuid AS user_uuid,\n au.name AS user_name\n FROM af_published_view_reaction avr\n INNER JOIN af_user au ON avr.created_by = au.uid\n WHERE comment_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "comment_id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "reaction_type", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "user_uuid", - "type_info": "Uuid" - }, - { - "ordinal": 3, - "name": "user_name", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "f9a80c40a2dea06b391a065c946472ff8e1a8e63425155e88b472b91c1e24f3a" -} diff --git a/libs/database/src/workspace.rs b/libs/database/src/workspace.rs index 87010178..8d14cbd3 100644 --- a/libs/database/src/workspace.rs +++ b/libs/database/src/workspace.rs @@ -3,6 +3,7 @@ use database_entity::dto::{ GlobalComment, PublishCollabItem, PublishInfo, Reaction, }; use futures_util::stream::BoxStream; +use serde::Serialize; use sqlx::{types::uuid, Executor, PgPool, Postgres, Transaction}; use std::{collections::HashMap, ops::DerefMut}; use tracing::{event, instrument}; @@ -1228,13 +1229,16 @@ pub async fn update_comment_deletion_status<'a, E: Executor<'a, Database = Postg Ok(()) } -#[derive(PartialEq, Eq, Hash)] -struct ReactionKey { - comment_id: Uuid, - reaction_type: String, +#[derive(sqlx::Type, Serialize, Debug)] +struct AFWebUserRow { + uuid: Uuid, + name: String, } -pub async fn select_reactions_for_published_view<'a, E: Executor<'a, Database = Postgres>>( +pub async fn select_reactions_for_published_view_ordered_by_reaction_type_creation_time< + 'a, + E: Executor<'a, Database = Postgres>, +>( executor: E, view_id: &Uuid, ) -> Result, AppError> { @@ -1243,89 +1247,76 @@ pub async fn select_reactions_for_published_view<'a, E: Executor<'a, Database = SELECT avr.comment_id, avr.reaction_type, - au.uuid AS user_uuid, - au.name AS user_name + MIN(avr.created_at) AS reaction_type_creation_at, + ARRAY_AGG((au.uuid, au.name)) AS "users!: Vec" FROM af_published_view_reaction avr INNER JOIN af_user au ON avr.created_by = au.uid WHERE view_id = $1 + GROUP BY comment_id, reaction_type + ORDER BY reaction_type_creation_at "#, view_id, ) .fetch_all(executor) .await?; - let reaction_to_users_map: HashMap> = rows.iter().fold( - HashMap::new(), - |mut acc: HashMap>, row| { - let users = acc - .entry(ReactionKey { - comment_id: row.comment_id, - reaction_type: row.reaction_type.clone(), - }) - .or_default(); - users.push(AFWebUser { - uid: row.user_uuid, - name: row.user_name.clone(), - avatar_url: None, - }); - acc - }, - ); - let reactions = reaction_to_users_map + + let reactions = rows .iter() - .map( - |( - ReactionKey { - comment_id, - reaction_type, - }, - users, - )| Reaction { - comment_id: *comment_id, - reaction_type: reaction_type.clone(), - react_users: users.clone(), - }, - ) + .map(|r| Reaction { + reaction_type: r.reaction_type.clone(), + react_users: r + .users + .iter() + .map(|u| AFWebUser { + uid: u.uuid, + name: u.name.clone(), + avatar_url: None, + }) + .collect(), + comment_id: r.comment_id, + }) .collect(); Ok(reactions) } -pub async fn select_reactions_for_comment<'a, E: Executor<'a, Database = Postgres>>( +pub async fn select_reactions_for_comment_ordered_by_reaction_type_creation_time< + 'a, + E: Executor<'a, Database = Postgres>, +>( executor: E, comment_id: &Uuid, ) -> Result, AppError> { let rows = sqlx::query!( r#" SELECT - avr.comment_id, avr.reaction_type, - au.uuid AS user_uuid, - au.name AS user_name + MIN(avr.created_at) AS reaction_type_creation_at, + ARRAY_AGG((au.uuid, au.name)) AS "users!: Vec" FROM af_published_view_reaction avr INNER JOIN af_user au ON avr.created_by = au.uid WHERE comment_id = $1 + GROUP BY reaction_type + ORDER BY reaction_type_creation_at "#, comment_id, ) .fetch_all(executor) .await?; - let reaction_type_to_users_map: HashMap> = rows.iter().fold( - HashMap::new(), - |mut acc: HashMap>, row| { - let users = acc.entry(row.reaction_type.clone()).or_default(); - users.push(AFWebUser { - uid: row.user_uuid, - name: row.user_name.clone(), - avatar_url: None, - }); - acc - }, - ); - let reactions = reaction_type_to_users_map + + let reactions = rows .iter() - .map(|(reaction_type, users)| Reaction { - reaction_type: reaction_type.clone(), - react_users: users.clone(), + .map(|r| Reaction { + reaction_type: r.reaction_type.clone(), + react_users: r + .users + .iter() + .map(|u| AFWebUser { + uid: u.uuid, + name: u.name.clone(), + avatar_url: None, + }) + .collect(), comment_id: *comment_id, }) .collect(); diff --git a/migrations/20240729065107_publish_view_reaction_2.sql b/migrations/20240729065107_publish_view_reaction_2.sql new file mode 100644 index 00000000..7dd1a40f --- /dev/null +++ b/migrations/20240729065107_publish_view_reaction_2.sql @@ -0,0 +1 @@ +ALTER TABLE af_published_view_reaction ADD COLUMN created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/src/biz/workspace/ops.rs b/src/biz/workspace/ops.rs index 9eb355b9..504e7a9e 100644 --- a/src/biz/workspace/ops.rs +++ b/src/biz/workspace/ops.rs @@ -198,8 +198,14 @@ pub async fn get_reactions_on_published_view( comment_id: &Option, ) -> Result, AppError> { let reaction = match comment_id { - Some(comment_id) => select_reactions_for_comment(pg_pool, comment_id).await?, - None => select_reactions_for_published_view(pg_pool, view_id).await?, + Some(comment_id) => { + select_reactions_for_comment_ordered_by_reaction_type_creation_time(pg_pool, comment_id) + .await? + }, + None => { + select_reactions_for_published_view_ordered_by_reaction_type_creation_time(pg_pool, view_id) + .await? + }, }; Ok(reaction) } diff --git a/tests/workspace/publish.rs b/tests/workspace/publish.rs index 27283d9f..b1647c8d 100644 --- a/tests/workspace/publish.rs +++ b/tests/workspace/publish.rs @@ -465,12 +465,13 @@ async fn test_publish_reactions() { assert_eq!(result.unwrap_err().code, ErrorCode::NotLoggedIn); let (user_client, _) = generate_unique_registered_user_client().await; + sleep(Duration::from_millis(1)); user_client - .create_reaction_on_comment(like_emoji, &view_id, &likable_comment_id) + .create_reaction_on_comment(party_emoji, &view_id, &party_comment_id) .await .unwrap(); user_client - .create_reaction_on_comment(party_emoji, &view_id, &party_comment_id) + .create_reaction_on_comment(like_emoji, &view_id, &likable_comment_id) .await .unwrap(); @@ -479,6 +480,8 @@ async fn test_publish_reactions() { .await .unwrap() .reactions; + assert_eq!(reactions[0].reaction_type, like_emoji); + assert_eq!(reactions[1].reaction_type, party_emoji); let reaction_count: HashMap = reactions .iter() .map(|r| (r.reaction_type.clone(), r.react_users.len() as i32))