Skip to content

Commit 4fb65eb

Browse files
authored
Merge pull request #2755 from simonredfern/develop
Some denormalisations of chat room e.g.: last_message_at,
2 parents 46ec8e0 + bba090d commit 4fb65eb

File tree

7 files changed

+214
-8
lines changed

7 files changed

+214
-8
lines changed

obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala

Lines changed: 149 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12830,6 +12830,10 @@ trait APIMethods600 {
1283012830
created_by_provider = "https://github.com",
1283112831
is_open_room = false,
1283212832
is_archived = false,
12833+
last_message_at = Some(new java.util.Date()),
12834+
last_message_preview = Some("Hello everyone!"),
12835+
last_message_sender = Some("robert.x.0.gh"),
12836+
unread_count = Some(3),
1283312837
created_at = new java.util.Date(),
1283412838
updated_at = new java.util.Date()
1283512839
),
@@ -12897,6 +12901,10 @@ trait APIMethods600 {
1289712901
created_by_provider = "https://github.com",
1289812902
is_open_room = false,
1289912903
is_archived = false,
12904+
last_message_at = Some(new java.util.Date()),
12905+
last_message_preview = Some("Hello everyone!"),
12906+
last_message_sender = Some("robert.x.0.gh"),
12907+
unread_count = Some(3),
1290012908
created_at = new java.util.Date(),
1290112909
updated_at = new java.util.Date()
1290212910
),
@@ -12963,6 +12971,10 @@ trait APIMethods600 {
1296312971
created_by_provider = "https://github.com",
1296412972
is_open_room = false,
1296512973
is_archived = false,
12974+
last_message_at = Some(new java.util.Date()),
12975+
last_message_preview = Some("Hello everyone!"),
12976+
last_message_sender = Some("robert.x.0.gh"),
12977+
unread_count = Some(3),
1296612978
created_at = new java.util.Date(),
1296712979
updated_at = new java.util.Date()
1296812980
))),
@@ -12984,8 +12996,11 @@ trait APIMethods600 {
1298412996
} map {
1298512997
x => unboxFullOrFail(x, callContext, s"$UnknownError Cannot get chat rooms", 400)
1298612998
}
12999+
unreadCounts <- Future {
13000+
computeUnreadCounts(rooms, u.userId)
13001+
}
1298713002
} yield {
12988-
(JSONFactory600.createChatRoomsJson(rooms), HttpCode.`200`(callContext))
13003+
(JSONFactory600.createChatRoomsJson(rooms, unreadCounts), HttpCode.`200`(callContext))
1298913004
}
1299013005
}
1299113006
}
@@ -13015,6 +13030,10 @@ trait APIMethods600 {
1301513030
created_by_provider = "https://github.com",
1301613031
is_open_room = false,
1301713032
is_archived = false,
13033+
last_message_at = Some(new java.util.Date()),
13034+
last_message_preview = Some("Hello everyone!"),
13035+
last_message_sender = Some("robert.x.0.gh"),
13036+
unread_count = Some(3),
1301813037
created_at = new java.util.Date(),
1301913038
updated_at = new java.util.Date()
1302013039
))),
@@ -13036,8 +13055,11 @@ trait APIMethods600 {
1303613055
} map {
1303713056
x => unboxFullOrFail(x, callContext, s"$UnknownError Cannot get chat rooms", 400)
1303813057
}
13058+
unreadCounts <- Future {
13059+
computeUnreadCounts(rooms, u.userId)
13060+
}
1303913061
} yield {
13040-
(JSONFactory600.createChatRoomsJson(rooms), HttpCode.`200`(callContext))
13062+
(JSONFactory600.createChatRoomsJson(rooms, unreadCounts), HttpCode.`200`(callContext))
1304113063
}
1304213064
}
1304313065
}
@@ -13067,6 +13089,10 @@ trait APIMethods600 {
1306713089
created_by_provider = "https://github.com",
1306813090
is_open_room = false,
1306913091
is_archived = false,
13092+
last_message_at = Some(new java.util.Date()),
13093+
last_message_preview = Some("Hello everyone!"),
13094+
last_message_sender = Some("robert.x.0.gh"),
13095+
unread_count = Some(3),
1307013096
created_at = new java.util.Date(),
1307113097
updated_at = new java.util.Date()
1307213098
),
@@ -13126,6 +13152,10 @@ trait APIMethods600 {
1312613152
created_by_provider = "https://github.com",
1312713153
is_open_room = false,
1312813154
is_archived = false,
13155+
last_message_at = Some(new java.util.Date()),
13156+
last_message_preview = Some("Hello everyone!"),
13157+
last_message_sender = Some("robert.x.0.gh"),
13158+
unread_count = Some(3),
1312913159
created_at = new java.util.Date(),
1313013160
updated_at = new java.util.Date()
1313113161
),
@@ -13185,6 +13215,10 @@ trait APIMethods600 {
1318513215
created_by_provider = "https://github.com",
1318613216
is_open_room = false,
1318713217
is_archived = false,
13218+
last_message_at = Some(new java.util.Date()),
13219+
last_message_preview = Some("Hello everyone!"),
13220+
last_message_sender = Some("robert.x.0.gh"),
13221+
unread_count = Some(3),
1318813222
created_at = new java.util.Date(),
1318913223
updated_at = new java.util.Date()
1319013224
),
@@ -13254,6 +13288,10 @@ trait APIMethods600 {
1325413288
created_by_provider = "https://github.com",
1325513289
is_open_room = false,
1325613290
is_archived = false,
13291+
last_message_at = Some(new java.util.Date()),
13292+
last_message_preview = Some("Hello everyone!"),
13293+
last_message_sender = Some("robert.x.0.gh"),
13294+
unread_count = Some(3),
1325713295
created_at = new java.util.Date(),
1325813296
updated_at = new java.util.Date()
1325913297
),
@@ -13418,6 +13456,10 @@ trait APIMethods600 {
1341813456
created_by_provider = "https://github.com",
1341913457
is_open_room = false,
1342013458
is_archived = true,
13459+
last_message_at = Some(new java.util.Date()),
13460+
last_message_preview = Some("Hello everyone!"),
13461+
last_message_sender = Some("robert.x.0.gh"),
13462+
unread_count = Some(3),
1342113463
created_at = new java.util.Date(),
1342213464
updated_at = new java.util.Date()
1342313465
),
@@ -13479,6 +13521,10 @@ trait APIMethods600 {
1347913521
created_by_provider = "https://github.com",
1348013522
is_open_room = false,
1348113523
is_archived = true,
13524+
last_message_at = Some(new java.util.Date()),
13525+
last_message_preview = Some("Hello everyone!"),
13526+
last_message_sender = Some("robert.x.0.gh"),
13527+
unread_count = Some(3),
1348213528
created_at = new java.util.Date(),
1348313529
updated_at = new java.util.Date()
1348413530
),
@@ -13543,6 +13589,10 @@ trait APIMethods600 {
1354313589
created_by_provider = "provider",
1354413590
is_open_room = true,
1354513591
is_archived = false,
13592+
last_message_at = Some(new java.util.Date()),
13593+
last_message_preview = Some("Hello everyone!"),
13594+
last_message_sender = Some("robert.x.0.gh"),
13595+
unread_count = Some(3),
1354613596
created_at = new java.util.Date(),
1354713597
updated_at = new java.util.Date()
1354813598
),
@@ -13608,6 +13658,10 @@ trait APIMethods600 {
1360813658
created_by_provider = "provider",
1360913659
is_open_room = true,
1361013660
is_archived = false,
13661+
last_message_at = Some(new java.util.Date()),
13662+
last_message_preview = Some("Hello everyone!"),
13663+
last_message_sender = Some("robert.x.0.gh"),
13664+
unread_count = Some(3),
1361113665
created_at = new java.util.Date(),
1361213666
updated_at = new java.util.Date()
1361313667
),
@@ -16252,6 +16306,10 @@ trait APIMethods600 {
1625216306
created_by_provider = "https://github.com",
1625316307
is_open_room = false,
1625416308
is_archived = false,
16309+
last_message_at = Some(new java.util.Date()),
16310+
last_message_preview = Some("Hello everyone!"),
16311+
last_message_sender = Some("robert.x.0.gh"),
16312+
unread_count = Some(3),
1625516313
created_at = new java.util.Date(),
1625616314
updated_at = new java.util.Date()
1625716315
))),
@@ -16273,13 +16331,22 @@ trait APIMethods600 {
1627316331
} map {
1627416332
x => unboxFullOrFail(x, callContext, s"$UnknownError Cannot get participant records", 400)
1627516333
}
16276-
rooms <- Future {
16334+
roomsAndCounts <- Future {
1627716335
participantRecords.flatMap { p =>
16278-
code.chat.ChatRoomTrait.chatRoomProvider.vend.getChatRoom(p.chatRoomId).toList
16336+
code.chat.ChatRoomTrait.chatRoomProvider.vend.getChatRoom(p.chatRoomId).toList.map { room =>
16337+
val count = if (room.isOpenRoom) {
16338+
code.chat.ChatMessageTrait.chatMessageProvider.vend.getUnreadMentionCount(p.chatRoomId, p.userId, p.lastReadAt)
16339+
} else {
16340+
code.chat.ChatMessageTrait.chatMessageProvider.vend.getUnreadCount(p.chatRoomId, p.userId, p.lastReadAt)
16341+
}
16342+
(room, count.openOr(0L))
16343+
}
1627916344
}
1628016345
}
1628116346
} yield {
16282-
(JSONFactory600.createChatRoomsJson(rooms), HttpCode.`200`(callContext))
16347+
val rooms = roomsAndCounts.map(_._1)
16348+
val unreadCounts = roomsAndCounts.map { case (room, count) => room.chatRoomId -> count }.toMap
16349+
(JSONFactory600.createChatRoomsJson(rooms, unreadCounts), HttpCode.`200`(callContext))
1628316350
}
1628416351
}
1628516352
}
@@ -16463,6 +16530,83 @@ trait APIMethods600 {
1646316530
}
1646416531
}
1646516532

16533+
// 29. getBulkReactions
16534+
staticResourceDocs += ResourceDoc(
16535+
getBulkReactions,
16536+
implementedInApiVersion,
16537+
nameOf(getBulkReactions),
16538+
"GET",
16539+
"/chat-rooms/CHAT_ROOM_ID/messages/reactions",
16540+
"Get Bulk Reactions",
16541+
s"""Get reactions for multiple messages in a single request.
16542+
|
16543+
|Pass message IDs as a comma-separated query parameter: ?message_ids=id1,id2,id3
16544+
|
16545+
|Returns reactions grouped by message ID.
16546+
|
16547+
|Authentication is Required
16548+
|
16549+
|""".stripMargin,
16550+
EmptyBody,
16551+
BulkReactionsJsonV600(message_reactions = List(MessageReactionsJsonV600(
16552+
chat_message_id = "msg-id-123",
16553+
reactions = List(ReactionSummaryJsonV600(emoji = "thumbsup", count = 2, user_ids = List("user-1", "user-2")))
16554+
))),
16555+
List(
16556+
$AuthenticatedUserIsRequired,
16557+
ChatRoomNotFound,
16558+
NotChatRoomParticipant,
16559+
UnknownError
16560+
),
16561+
List(apiTagChat),
16562+
None
16563+
)
16564+
16565+
lazy val getBulkReactions: OBPEndpoint = {
16566+
case "chat-rooms" :: chatRoomId :: "messages" :: "reactions" :: Nil JsonGet _ => {
16567+
cc => implicit val ec = EndpointContext(Some(cc))
16568+
for {
16569+
(Full(u), callContext) <- authenticatedAccess(cc)
16570+
room <- Future {
16571+
code.chat.ChatRoomTrait.chatRoomProvider.vend.getChatRoom(chatRoomId)
16572+
} map {
16573+
x => unboxFullOrFail(x, callContext, ChatRoomNotFound, 404)
16574+
}
16575+
_ <- Future {
16576+
code.chat.ChatPermissions.isParticipant(chatRoomId, u.userId)
16577+
} map {
16578+
x => unboxFullOrFail(x, callContext, NotChatRoomParticipant, 403)
16579+
}
16580+
messageIds = ObpS.param("message_ids").map(_.split(",").map(_.trim).filter(_.nonEmpty).toList).getOrElse(List.empty)
16581+
allReactions <- Future {
16582+
code.chat.ReactionTrait.reactionProvider.vend.getReactionsForMessages(messageIds)
16583+
} map {
16584+
x => unboxFullOrFail(x, callContext, s"$UnknownError Cannot get reactions", 400)
16585+
}
16586+
} yield {
16587+
(JSONFactory600.createBulkReactionsJson(allReactions, messageIds), HttpCode.`200`(callContext))
16588+
}
16589+
}
16590+
}
16591+
16592+
/**
16593+
* Compute unread counts for a list of rooms for a given user.
16594+
* For open rooms, counts only mentions. For private rooms, counts all unread messages.
16595+
*/
16596+
private def computeUnreadCounts(rooms: List[code.chat.ChatRoomTrait], userId: String): Map[String, Long] = {
16597+
rooms.flatMap { room =>
16598+
val participant = code.chat.ChatPermissions.isParticipant(room.chatRoomId, userId)
16599+
participant.toList.map { p =>
16600+
val count = if (room.isOpenRoom) {
16601+
code.chat.ChatMessageTrait.chatMessageProvider.vend.getUnreadMentionCount(room.chatRoomId, userId, p.lastReadAt)
16602+
} else {
16603+
code.chat.ChatMessageTrait.chatMessageProvider.vend.getUnreadCount(room.chatRoomId, userId, p.lastReadAt)
16604+
}
16605+
room.chatRoomId -> count.openOr(0L)
16606+
}
16607+
}.toMap
16608+
}
16609+
1646616610
}
1646716611
}
1646816612

obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,10 @@ case class ChatRoomJsonV600(
11921192
created_by_provider: String,
11931193
is_open_room: Boolean,
11941194
is_archived: Boolean,
1195+
last_message_at: Option[java.util.Date],
1196+
last_message_preview: Option[String],
1197+
last_message_sender: Option[String],
1198+
unread_count: Option[Long],
11951199
created_at: java.util.Date,
11961200
updated_at: java.util.Date
11971201
)
@@ -1251,6 +1255,9 @@ case class TypingUsersJsonV600(users: List[TypingUserJsonV600])
12511255
case class UnreadCountJsonV600(chat_room_id: String, unread_count: Long)
12521256
case class UnreadCountsJsonV600(unread_counts: List[UnreadCountJsonV600])
12531257

1258+
case class MessageReactionsJsonV600(chat_message_id: String, reactions: List[ReactionSummaryJsonV600])
1259+
case class BulkReactionsJsonV600(message_reactions: List[MessageReactionsJsonV600])
1260+
12541261
case class JoiningKeyJsonV600(joining_key: String)
12551262

12561263
object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
@@ -2961,8 +2968,9 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
29612968
}
29622969

29632970
// Chat / Messaging factory functions
2964-
def createChatRoomJson(room: code.chat.ChatRoomTrait): ChatRoomJsonV600 = {
2971+
def createChatRoomJson(room: code.chat.ChatRoomTrait, unreadCount: Option[Long] = None): ChatRoomJsonV600 = {
29652972
val creator = code.users.Users.users.vend.getUserByUserId(room.createdBy)
2973+
val hasLastMessage = room.lastMessageAt.isDefined
29662974
ChatRoomJsonV600(
29672975
chat_room_id = room.chatRoomId,
29682976
bank_id = room.bankId,
@@ -2974,12 +2982,16 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
29742982
created_by_provider = creator.map(_.provider).getOrElse(""),
29752983
is_open_room = room.isOpenRoom,
29762984
is_archived = room.isArchived,
2985+
last_message_at = room.lastMessageAt,
2986+
last_message_preview = if (hasLastMessage) Some(room.lastMessagePreview) else None,
2987+
last_message_sender = if (hasLastMessage) Some(room.lastMessageSender) else None,
2988+
unread_count = unreadCount,
29772989
created_at = room.createdDate,
29782990
updated_at = room.updatedDate
29792991
)
29802992
}
2981-
def createChatRoomsJson(rooms: List[code.chat.ChatRoomTrait]): ChatRoomsJsonV600 = {
2982-
ChatRoomsJsonV600(rooms.map(createChatRoomJson))
2993+
def createChatRoomsJson(rooms: List[code.chat.ChatRoomTrait], unreadCounts: Map[String, Long] = Map.empty): ChatRoomsJsonV600 = {
2994+
ChatRoomsJsonV600(rooms.map(r => createChatRoomJson(r, unreadCounts.get(r.chatRoomId))))
29832995
}
29842996

29852997
def createParticipantJson(p: code.chat.ParticipantTrait): ParticipantJsonV600 = {
@@ -3036,6 +3048,17 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
30363048
def createChatMessagesJson(messages: List[code.chat.ChatMessageTrait], allReactions: Map[String, List[code.chat.ReactionTrait]]): ChatMessagesJsonV600 = {
30373049
ChatMessagesJsonV600(messages.map(msg => createChatMessageJson(msg, allReactions.getOrElse(msg.chatMessageId, List.empty))))
30383050
}
3051+
def createBulkReactionsJson(allReactions: Map[String, List[code.chat.ReactionTrait]], messageIds: List[String]): BulkReactionsJsonV600 = {
3052+
BulkReactionsJsonV600(
3053+
message_reactions = messageIds.map { msgId =>
3054+
val reactions = allReactions.getOrElse(msgId, List.empty)
3055+
val summaries = reactions.groupBy(_.emoji).map { case (emoji, rs) =>
3056+
ReactionSummaryJsonV600(emoji = emoji, count = rs.size, user_ids = rs.map(_.userId))
3057+
}.toList
3058+
MessageReactionsJsonV600(chat_message_id = msgId, reactions = summaries)
3059+
}
3060+
)
3061+
}
30393062

30403063
def createChatMessagesJsonFromRows(
30413064
messages: List[code.chat.DoobieChatMessageQueries.ChatMessageRow],

obp-api/src/main/scala/code/chat/ChatEventPublisher.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ object ChatEventPublisher extends MdcLoggable {
6363
publishMessageEvent("new", msg, senderUsername, senderProvider, senderConsumerName)
6464
// Sending a message means the sender has "read" the room up to this point
6565
ParticipantTrait.participantProvider.vend.updateLastReadAt(msg.chatRoomId, msg.senderUserId)
66+
// Denormalize last message info onto the ChatRoom for efficient listing
67+
ChatRoomTrait.chatRoomProvider.vend.updateLastMessageInfo(
68+
msg.chatRoomId,
69+
msg.createdDate,
70+
if (msg.isDeleted) "" else msg.content,
71+
senderUsername
72+
)
6673
broadcastUnreadCounts(msg)
6774
}
6875

obp-api/src/main/scala/code/chat/ChatRoomTrait.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ trait ChatRoomProvider {
3030
): Box[ChatRoomTrait]
3131

3232
def setIsOpenRoom(chatRoomId: String, isOpenRoom: Boolean): Box[ChatRoomTrait]
33+
def updateLastMessageInfo(chatRoomId: String, lastMessageAt: Date, preview: String, senderUsername: String): Box[ChatRoomTrait]
3334
def archiveChatRoom(chatRoomId: String): Box[ChatRoomTrait]
3435
def deleteChatRoom(chatRoomId: String): Box[Boolean]
3536
def refreshJoiningKey(chatRoomId: String): Box[ChatRoomTrait]
@@ -46,6 +47,9 @@ trait ChatRoomTrait {
4647
/** Whether this is an "open room" where all users are implicit participants. */
4748
def isOpenRoom: Boolean
4849
def isArchived: Boolean
50+
def lastMessageAt: Option[Date]
51+
def lastMessagePreview: String
52+
def lastMessageSender: String
4953
def createdDate: Date
5054
def updatedDate: Date
5155
}

0 commit comments

Comments
 (0)