diff --git a/comms/db/queries/get_chat_messages.go b/comms/db/queries/get_chat_messages.go index 37592513114..58614713d6c 100644 --- a/comms/db/queries/get_chat_messages.go +++ b/comms/db/queries/get_chat_messages.go @@ -31,7 +31,13 @@ SELECT chat_message.message_id, chat_message.chat_id, chat_message.user_id, chat FROM chat_message JOIN chat_member ON chat_message.chat_id = chat_member.chat_id LEFT JOIN chat_message_reactions reactions ON chat_message.message_id = reactions.message_id -WHERE chat_member.user_id = $1 AND chat_message.chat_id = $2 AND chat_message.created_at < $4 AND (chat_member.cleared_history_at IS NULL OR chat_message.created_at > chat_member.cleared_history_at) +WHERE chat_member.user_id = $1 + AND chat_message.chat_id = $2 + AND chat_message.created_at < $4 + AND chat_message.created_at > $5 + AND (chat_member.cleared_history_at IS NULL + OR chat_message.created_at > chat_member.cleared_history_at + ) GROUP BY chat_message.message_id ORDER BY chat_message.created_at DESC, chat_message.message_id LIMIT $3 @@ -41,7 +47,8 @@ type ChatMessagesAndReactionsParams struct { UserID int32 `db:"user_id" json:"user_id"` ChatID string `db:"chat_id" json:"chat_id"` Limit int32 `json:"limit"` - Cursor time.Time `json:"cursor"` + Before time.Time `json:"before"` + After time.Time `json:"after"` } type ChatMessageAndReactionsRow struct { @@ -98,7 +105,8 @@ func ChatMessagesAndReactions(q db.Queryable, ctx context.Context, arg ChatMessa arg.UserID, arg.ChatID, arg.Limit, - arg.Cursor, + arg.Before, + arg.After, ) return rows, err } diff --git a/comms/db/queries/get_summaries.go b/comms/db/queries/get_summaries.go index 12fa7ef4dcb..1de3a8c0e3e 100644 --- a/comms/db/queries/get_summaries.go +++ b/comms/db/queries/get_summaries.go @@ -8,32 +8,38 @@ import ( ) type SummaryRow struct { - TotalCount int64 `db:"total_count" json:"total_count"` - RemainingCount int64 `db:"remaining_count" json:"remaining_count"` + TotalCount int64 `db:"total_count" json:"total_count"` + BeforeCount int64 `db:"before_count" json:"before_count"` + AfterCount int64 `db:"after_count" json:"after_count"` } const chatMessagesSummary = ` WITH messages AS ( SELECT - chat_message.message_id, chat_message.chat_id, chat_message.user_id, chat_message.created_at, chat_message.ciphertext + chat_message.message_id, chat_message.created_at FROM chat_message JOIN chat_member ON chat_message.chat_id = chat_member.chat_id - WHERE chat_member.user_id = $1 AND chat_message.chat_id = $2 AND (chat_member.cleared_history_at IS NULL OR chat_message.created_at > chat_member.cleared_history_at) + WHERE chat_member.user_id = $1 + AND chat_message.chat_id = $2 + AND (chat_member.cleared_history_at IS NULL + OR chat_message.created_at > chat_member.cleared_history_at) ) SELECT (SELECT COUNT(*) AS total_count FROM messages), - (SELECT COUNT(*) FROM messages WHERE created_at < $3) AS remaining_count + (SELECT COUNT(*) FROM messages WHERE created_at < $3) AS before_count, + (SELECT COUNT(*) FROM messages WHERE created_at > $4) AS after_count ` type ChatMessagesSummaryParams struct { UserID int32 `db:"user_id" json:"user_id"` ChatID string `db:"chat_id" json:"chat_id"` - Cursor time.Time `json:"cursor"` + Before time.Time `json:"before"` + After time.Time `json:"after"` } func ChatMessagesSummary(q db.Queryable, ctx context.Context, arg ChatMessagesSummaryParams) (SummaryRow, error) { var summary SummaryRow - err := q.GetContext(ctx, &summary, chatMessagesSummary, arg.UserID, arg.ChatID, arg.Cursor) + err := q.GetContext(ctx, &summary, chatMessagesSummary, arg.UserID, arg.ChatID, arg.Before, arg.After) return summary, err } @@ -51,16 +57,16 @@ SELECT ( SELECT COUNT(*) FROM user_chats WHERE last_message_at < $1 - ) AS remaining_count + ) AS before_count ` type UserChatsSummaryParams struct { - Cursor time.Time `json:"cursor"` + Before time.Time `json:"before"` UserID int32 `db:"user_id" json:"user_id"` } func UserChatsSummary(q db.Queryable, ctx context.Context, arg UserChatsSummaryParams) (SummaryRow, error) { var summary SummaryRow - err := q.GetContext(ctx, &summary, userChatsSummary, arg.Cursor, arg.UserID) + err := q.GetContext(ctx, &summary, userChatsSummary, arg.Before, arg.UserID) return summary, err } diff --git a/comms/misc/hashids_util.go b/comms/misc/hashids_util.go index d625de11aaf..41dc7f6df60 100644 --- a/comms/misc/hashids_util.go +++ b/comms/misc/hashids_util.go @@ -21,5 +21,5 @@ func DecodeHashId(id string) (int, error) { return ids[0], err } func EncodeHashId(id int) (string, error) { - return hashIdUtil.Encode([]int{ id }) -} \ No newline at end of file + return hashIdUtil.Encode([]int{id}) +} diff --git a/comms/schema/schema.go b/comms/schema/schema.go index f73ec50f6da..9fe571dfdd5 100644 --- a/comms/schema/schema.go +++ b/comms/schema/schema.go @@ -163,75 +163,87 @@ type Health struct { } type Summary struct { - NextCursor string `json:"next_cursor"` - PrevCursor string `json:"prev_cursor"` - RemainingCount float64 `json:"remaining_count"` - TotalCount float64 `json:"total_count"` + NextCount float64 `json:"next_count"` + NextCursor string `json:"next_cursor"` + PrevCount float64 `json:"prev_count"` + PrevCursor string `json:"prev_cursor"` + TotalCount float64 `json:"total_count"` } type ChatCreateRPCMethod string + const ( MethodChatCreate ChatCreateRPCMethod = "chat.create" ) type ChatDeleteRPCMethod string + const ( MethodChatDelete ChatDeleteRPCMethod = "chat.delete" ) type ChatInviteRPCMethod string + const ( MethodChatInvite ChatInviteRPCMethod = "chat.invite" ) type ChatMessageRPCMethod string + const ( MethodChatMessage ChatMessageRPCMethod = "chat.message" ) type ChatReactRPCMethod string + const ( MethodChatReact ChatReactRPCMethod = "chat.react" ) type ChatReadRPCMethod string + const ( MethodChatRead ChatReadRPCMethod = "chat.read" ) type ChatBlockRPCMethod string + const ( MethodChatBlock ChatBlockRPCMethod = "chat.block" ) type ChatUnblockRPCMethod string + const ( MethodChatUnblock ChatUnblockRPCMethod = "chat.unblock" ) type ChatPermitRPCMethod string + const ( MethodChatPermit ChatPermitRPCMethod = "chat.permit" ) // Defines who the user allows to message them type ChatPermission string + const ( - All ChatPermission = "all" + All ChatPermission = "all" Followees ChatPermission = "followees" - None ChatPermission = "none" - Tippers ChatPermission = "tippers" + None ChatPermission = "none" + Tippers ChatPermission = "tippers" ) type RPCMethod string + const ( - RPCMethodChatBlock RPCMethod = "chat.block" - RPCMethodChatCreate RPCMethod = "chat.create" - RPCMethodChatDelete RPCMethod = "chat.delete" - RPCMethodChatInvite RPCMethod = "chat.invite" + RPCMethodChatBlock RPCMethod = "chat.block" + RPCMethodChatCreate RPCMethod = "chat.create" + RPCMethodChatDelete RPCMethod = "chat.delete" + RPCMethodChatInvite RPCMethod = "chat.invite" RPCMethodChatMessage RPCMethod = "chat.message" - RPCMethodChatPermit RPCMethod = "chat.permit" - RPCMethodChatReact RPCMethod = "chat.react" - RPCMethodChatRead RPCMethod = "chat.read" + RPCMethodChatPermit RPCMethod = "chat.permit" + RPCMethodChatReact RPCMethod = "chat.react" + RPCMethodChatRead RPCMethod = "chat.read" RPCMethodChatUnblock RPCMethod = "chat.unblock" ) diff --git a/comms/schema/schema.ts b/comms/schema/schema.ts index eed787c8b79..e67be5494cc 100644 --- a/comms/schema/schema.ts +++ b/comms/schema/schema.ts @@ -147,8 +147,9 @@ export type CommsResponse = { } summary?: { prev_cursor: string + prev_count: number next_cursor: string - remaining_count: number + next_count: number total_count: number } // Overridden in client types but left as any for the server. diff --git a/comms/server/response_mapper.go b/comms/server/response_mapper.go index 7966e0a9da0..e837b4052c3 100644 --- a/comms/server/response_mapper.go +++ b/comms/server/response_mapper.go @@ -42,11 +42,13 @@ func ToChatResponse(chat queries.UserChatRow, members []db.ChatMember) schema.Us return chatData } -func ToSummaryResponse(cursor string, summary queries.SummaryRow) schema.Summary { +func ToSummaryResponse(prevCursor string, nextCursor string, summary queries.SummaryRow) schema.Summary { responseSummary := schema.Summary{ - TotalCount: float64(summary.TotalCount), - RemainingCount: float64(summary.RemainingCount), - NextCursor: cursor, + TotalCount: float64(summary.TotalCount), + PrevCount: float64(summary.BeforeCount), + PrevCursor: prevCursor, + NextCount: float64(summary.AfterCount), + NextCursor: nextCursor, } return responseSummary } diff --git a/comms/server/server.go b/comms/server/server.go index 0f7d6bb7c95..e38b68a0c31 100644 --- a/comms/server/server.go +++ b/comms/server/server.go @@ -91,16 +91,18 @@ func getChats(c echo.Context) error { } responseData[i] = ToChatResponse(chats[i], members) } - cursorPos := time.Now().UTC() + // TODO: Get these from params and filter queries.UserChats similarly + beforeCursorPos := time.Now().UTC() + afterCursorPos := time.Now().UTC() if len(chats) > 0 { - lastChat := chats[len(chats)-1] - cursorPos = lastChat.LastMessageAt + beforeCursorPos = chats[len(chats)-1].LastMessageAt + afterCursorPos = chats[0].LastMessageAt } - summary, err := queries.UserChatsSummary(db.Conn, ctx, queries.UserChatsSummaryParams{UserID: userId, Cursor: cursorPos}) + summary, err := queries.UserChatsSummary(db.Conn, ctx, queries.UserChatsSummaryParams{UserID: userId, Before: beforeCursorPos}) if err != nil { return err } - responseSummary := ToSummaryResponse(cursorPos.Format(time.RFC3339Nano), summary) + responseSummary := ToSummaryResponse(beforeCursorPos.Format(time.RFC3339Nano), afterCursorPos.Format(time.RFC3339Nano), summary) response := schema.CommsResponse{ Health: getHealthStatus(), Data: responseData, @@ -148,13 +150,20 @@ func getMessages(c echo.Context) error { return c.String(400, "wallet not found: "+err.Error()) } - params := queries.ChatMessagesAndReactionsParams{UserID: int32(userId), ChatID: c.Param("id"), Cursor: time.Now().UTC(), Limit: 50} - if c.QueryParam("cursor") != "" { - cursor, err := time.Parse(time.RFC3339Nano, c.QueryParam("cursor")) + params := queries.ChatMessagesAndReactionsParams{UserID: int32(userId), ChatID: c.Param("id"), Before: time.Now().UTC(), After: time.Time{}, Limit: 50} + if c.QueryParam("before") != "" { + beforeCursor, err := time.Parse(time.RFC3339Nano, c.QueryParam("before")) if err != nil { return err } - params.Cursor = cursor + params.Before = beforeCursor + } + if c.QueryParam("after") != "" { + afterCursor, err := time.Parse(time.RFC3339Nano, c.QueryParam("after")) + if err != nil { + return err + } + params.After = afterCursor } if c.QueryParam("limit") != "" { limit, err := strconv.Atoi(c.QueryParam("limit")) @@ -169,15 +178,17 @@ func getMessages(c echo.Context) error { return err } - cursorPos := params.Cursor + beforeCursorPos := params.Before + afterCursorPos := params.After if len(messages) > 0 { - cursorPos = messages[len(messages)-1].CreatedAt + beforeCursorPos = messages[len(messages)-1].CreatedAt + afterCursorPos = messages[0].CreatedAt } - summary, err := queries.ChatMessagesSummary(db.Conn, ctx, queries.ChatMessagesSummaryParams{UserID: userId, ChatID: c.Param("id"), Cursor: cursorPos}) + summary, err := queries.ChatMessagesSummary(db.Conn, ctx, queries.ChatMessagesSummaryParams{UserID: userId, ChatID: c.Param("id"), Before: beforeCursorPos, After: afterCursorPos}) if err != nil { return err } - responseSummary := ToSummaryResponse(cursorPos.Format(time.RFC3339Nano), summary) + responseSummary := ToSummaryResponse(beforeCursorPos.Format(time.RFC3339Nano), afterCursorPos.Format(time.RFC3339Nano), summary) response := schema.CommsResponse{ Health: getHealthStatus(), Data: Map(messages, ToMessageResponse), diff --git a/comms/server/server_test.go b/comms/server/server_test.go index 6d253c74f94..e7a99199a87 100644 --- a/comms/server/server_test.go +++ b/comms/server/server_test.go @@ -95,8 +95,8 @@ func TestGetChats(t *testing.T) { // Create 2 chats chatId1 := "chat1" chatId2 := "chat2" - chat1CreatedAt := time.Now().UTC() - chat2CreatedAt := time.Now().UTC().Add(time.Minute * time.Duration(30)) + chat1CreatedAt := time.Now().UTC().Add(-time.Minute * time.Duration(60)) + chat2CreatedAt := time.Now().UTC().Add(-time.Minute * time.Duration(30)) _, err = tx.Exec("insert into chat (chat_id, created_at, last_message_at) values ($1, $2, $2), ($3, $4, $4)", chatId1, chat1CreatedAt, chatId2, chat2CreatedAt) assert.NoError(t, err) @@ -171,9 +171,11 @@ func TestGetChats(t *testing.T) { expectedChat1Data, } expectedSummary := schema.Summary{ - TotalCount: float64(2), - RemainingCount: float64(0), - NextCursor: chat1CreatedAt.Format(time.RFC3339Nano), + TotalCount: float64(2), + NextCount: float64(0), + NextCursor: chat2CreatedAt.Format(time.RFC3339Nano), + PrevCount: float64(0), + PrevCursor: chat1CreatedAt.Format(time.RFC3339Nano), } expectedResponse, err := json.Marshal( schema.CommsResponse{ @@ -337,9 +339,11 @@ func TestGetMessages(t *testing.T) { expectedMessage1Data, } expectedSummary := schema.Summary{ - TotalCount: float64(2), - RemainingCount: float64(0), - NextCursor: message1CreatedAt.Format(time.RFC3339Nano), + TotalCount: float64(2), + NextCount: float64(0), + NextCursor: message2CreatedAt.Format(time.RFC3339Nano), + PrevCount: float64(0), + PrevCursor: message1CreatedAt.Format(time.RFC3339Nano), } expectedResponse, err := json.Marshal( schema.CommsResponse{ diff --git a/libs/src/sdk/api/chats/serverTypes.ts b/libs/src/sdk/api/chats/serverTypes.ts index eed787c8b79..e67be5494cc 100644 --- a/libs/src/sdk/api/chats/serverTypes.ts +++ b/libs/src/sdk/api/chats/serverTypes.ts @@ -147,8 +147,9 @@ export type CommsResponse = { } summary?: { prev_cursor: string + prev_count: number next_cursor: string - remaining_count: number + next_count: number total_count: number } // Overridden in client types but left as any for the server.