github.com/status-im/status-go@v1.1.0/protocol/message_persistence.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "fmt" 8 "sort" 9 "strings" 10 11 "github.com/golang/protobuf/proto" 12 "github.com/lib/pq" 13 14 "github.com/status-im/status-go/protocol/common" 15 "github.com/status-im/status-go/protocol/protobuf" 16 ) 17 18 var basicMessagesSelectQuery = ` 19 SELECT %s %s 20 FROM user_messages m1 21 LEFT JOIN user_messages m2 22 ON m1.response_to = m2.id 23 LEFT JOIN contacts c 24 ON m1.source = c.id 25 LEFT JOIN discord_messages dm 26 ON m1.discord_message_id = dm.id 27 LEFT JOIN discord_message_authors dm_author 28 ON dm.author_id = dm_author.id 29 LEFT JOIN discord_message_attachments dm_attachment 30 ON dm.id = dm_attachment.discord_message_id 31 LEFT JOIN discord_messages m2_dm 32 ON m2.discord_message_id = m2_dm.id 33 LEFT JOIN discord_message_authors m2_dm_author 34 ON m2_dm.author_id = m2_dm_author.id 35 LEFT JOIN bridge_messages bm 36 ON m1.id = bm.user_messages_id 37 LEFT JOIN bridge_messages bm_response 38 ON m2.id = bm_response.user_messages_id 39 ` 40 41 var basicInsertDiscordMessageAuthorQuery = `INSERT OR REPLACE INTO discord_message_authors(id,name,discriminator,nickname,avatar_url, avatar_image_payload) VALUES (?,?,?,?,?,?)` 42 43 var cursor = "substr('0000000000000000000000000000000000000000000000000000000000000000' || m1.clock_value, -64, 64) || m1.id" 44 var cursorField = cursor + " as cursor" 45 46 var caseSensitiveSearchCond = "(m1.text LIKE '%' || ? || '%' OR bm.content LIKE '%' || ? || '%' OR dm.content LIKE '%' || ? || '%')" 47 var caseInsensitiveSearchCond = "(LOWER(m1.text) LIKE LOWER('%' || ? || '%') OR LOWER(bm.content) LIKE LOWER('%' || ? || '%') OR LOWER(dm.content) LIKE LOWER('%' || ? || '%'))" 48 49 func (db sqlitePersistence) buildMessagesQueryWithAdditionalFields(additionalSelectFields, whereAndTheRest string) string { 50 allFields := db.tableUserMessagesAllFieldsJoin() 51 if additionalSelectFields != "" { 52 additionalSelectFields = "," + additionalSelectFields 53 } 54 base := fmt.Sprintf(basicMessagesSelectQuery, allFields, additionalSelectFields) 55 return base + " " + whereAndTheRest 56 } 57 58 func (db sqlitePersistence) buildMessagesQuery(whereAndTheRest string) string { 59 return db.buildMessagesQueryWithAdditionalFields("", whereAndTheRest) 60 } 61 62 func (db sqlitePersistence) tableUserMessagesAllFields() string { 63 return `id, 64 whisper_timestamp, 65 source, 66 text, 67 content_type, 68 username, 69 timestamp, 70 chat_id, 71 local_chat_id, 72 message_type, 73 clock_value, 74 seen, 75 outgoing_status, 76 parsed_text, 77 sticker_pack, 78 sticker_hash, 79 image_payload, 80 image_type, 81 album_id, 82 album_images, 83 album_images_count, 84 image_width, 85 image_height, 86 image_base64, 87 audio_payload, 88 audio_type, 89 audio_duration_ms, 90 audio_base64, 91 community_id, 92 mentions, 93 links, 94 unfurled_links, 95 unfurled_status_links, 96 command_id, 97 command_value, 98 command_from, 99 command_address, 100 command_contract, 101 command_transaction_hash, 102 command_state, 103 command_signature, 104 replace_message, 105 edited_at, 106 deleted, 107 deleted_by, 108 deleted_for_me, 109 rtl, 110 line_count, 111 response_to, 112 gap_from, 113 gap_to, 114 contact_request_state, 115 contact_verification_status, 116 mentioned, 117 replied, 118 discord_message_id` 119 } 120 121 // keep the same order as in tableUserMessagesScanAllFields 122 func (db sqlitePersistence) tableUserMessagesAllFieldsJoin() string { 123 return `m1.id, 124 m1.whisper_timestamp, 125 m1.source, 126 m1.text, 127 m1.content_type, 128 m1.username, 129 m1.timestamp, 130 m1.chat_id, 131 m1.local_chat_id, 132 m1.message_type, 133 m1.clock_value, 134 m1.seen, 135 m1.outgoing_status, 136 m1.parsed_text, 137 m1.sticker_pack, 138 m1.sticker_hash, 139 m1.image_payload, 140 m1.image_type, 141 COALESCE(m1.album_id, ""), 142 COALESCE(m1.album_images_count, 0), 143 COALESCE(m1.image_width, 0), 144 COALESCE(m1.image_height, 0), 145 COALESCE(m1.audio_duration_ms,0), 146 m1.community_id, 147 m1.mentions, 148 m1.links, 149 m1.unfurled_links, 150 m1.unfurled_status_links, 151 m1.command_id, 152 m1.command_value, 153 m1.command_from, 154 m1.command_address, 155 m1.command_contract, 156 m1.command_transaction_hash, 157 m1.command_state, 158 m1.command_signature, 159 m1.replace_message, 160 m1.edited_at, 161 m1.deleted, 162 m1.deleted_by, 163 m1.deleted_for_me, 164 m1.rtl, 165 m1.line_count, 166 m1.response_to, 167 m1.gap_from, 168 m1.gap_to, 169 m1.contact_request_state, 170 m1.contact_verification_status, 171 m1.mentioned, 172 m1.replied, 173 COALESCE(m1.discord_message_id, ""), 174 COALESCE(dm.author_id, ""), 175 COALESCE(dm.type, ""), 176 COALESCE(dm.timestamp, ""), 177 COALESCE(dm.timestamp_edited, ""), 178 COALESCE(dm.content, ""), 179 COALESCE(dm.reference_message_id, ""), 180 COALESCE(dm.reference_channel_id, ""), 181 COALESCE(dm_author.name, ""), 182 COALESCE(dm_author.discriminator, ""), 183 COALESCE(dm_author.nickname, ""), 184 COALESCE(dm_author.avatar_url, ""), 185 COALESCE(dm_attachment.id, ""), 186 COALESCE(dm_attachment.discord_message_id, ""), 187 COALESCE(dm_attachment.url, ""), 188 COALESCE(dm_attachment.file_name, ""), 189 COALESCE(dm_attachment.content_type, ""), 190 m2.source, 191 m2.text, 192 m2.parsed_text, 193 m2.album_images, 194 m2.album_images_count, 195 m2.audio_duration_ms, 196 m2.community_id, 197 m2.id, 198 m2.content_type, 199 m2.deleted, 200 m2.deleted_for_me, 201 c.alias, 202 c.identicon, 203 COALESCE(m2.discord_message_id, ""), 204 COALESCE(m2_dm_author.name, ""), 205 COALESCE(m2_dm_author.nickname, ""), 206 COALESCE(m2_dm_author.avatar_url, ""), 207 COALESCE(bm.bridge_name, ""), 208 COALESCE(bm.user_name, ""), 209 COALESCE(bm.user_avatar, ""), 210 COALESCE(bm.user_id, ""), 211 COALESCE(bm.content, ""), 212 COALESCE(bm.message_id, ""), 213 COALESCE(bm.parent_message_id, ""), 214 COALESCE(bm_response.bridge_name, ""), 215 COALESCE(bm_response.user_name, ""), 216 COALESCE(bm_response.user_avatar, ""), 217 COALESCE(bm_response.user_id, ""), 218 COALESCE(bm_response.content, "")` 219 } 220 221 func (db sqlitePersistence) tableUserMessagesAllFieldsCount() int { 222 return strings.Count(db.tableUserMessagesAllFields(), ",") + 1 223 } 224 225 type scanner interface { 226 Scan(dest ...interface{}) error 227 } 228 229 // keep the same order as in tableUserMessagesAllFieldsJoin 230 func (db sqlitePersistence) tableUserMessagesScanAllFields(row scanner, message *common.Message, others ...interface{}) error { 231 var quotedID sql.NullString 232 var ContentType sql.NullInt64 233 var quotedText sql.NullString 234 var quotedParsedText []byte 235 var quotedAlbumImages []byte 236 var quotedAlbumImagesCount sql.NullInt64 237 var quotedFrom sql.NullString 238 var quotedAudioDuration sql.NullInt64 239 var quotedCommunityID sql.NullString 240 var quotedDeleted sql.NullBool 241 var quotedDeletedForMe sql.NullBool 242 var serializedMentions []byte 243 var serializedLinks []byte 244 var serializedUnfurledLinks []byte 245 var serializedUnfurledStatusLinks []byte 246 var alias sql.NullString 247 var identicon sql.NullString 248 var communityID sql.NullString 249 var gapFrom sql.NullInt64 250 var gapTo sql.NullInt64 251 var editedAt sql.NullInt64 252 var deleted sql.NullBool 253 var deletedBy sql.NullString 254 var deletedForMe sql.NullBool 255 var contactRequestState sql.NullInt64 256 var contactVerificationState sql.NullInt64 257 258 sticker := &protobuf.StickerMessage{} 259 command := &common.CommandParameters{} 260 audio := &protobuf.AudioMessage{} 261 image := &protobuf.ImageMessage{} 262 discordMessage := &protobuf.DiscordMessage{ 263 Author: &protobuf.DiscordMessageAuthor{}, 264 Reference: &protobuf.DiscordMessageReference{}, 265 Attachments: []*protobuf.DiscordMessageAttachment{}, 266 } 267 bridgeMessage := &protobuf.BridgeMessage{} 268 269 quotedBridgeMessage := &protobuf.BridgeMessage{} 270 271 quotedDiscordMessage := &protobuf.DiscordMessage{ 272 Author: &protobuf.DiscordMessageAuthor{}, 273 } 274 275 attachment := &protobuf.DiscordMessageAttachment{} 276 277 args := []interface{}{ 278 &message.ID, 279 &message.WhisperTimestamp, 280 &message.From, // source in table 281 &message.Text, 282 &message.ContentType, 283 &message.Alias, 284 &message.Timestamp, 285 &message.ChatId, 286 &message.LocalChatID, 287 &message.MessageType, 288 &message.Clock, 289 &message.Seen, 290 &message.OutgoingStatus, 291 &message.ParsedText, 292 &sticker.Pack, 293 &sticker.Hash, 294 &image.Payload, 295 &image.Format, 296 &image.AlbumId, 297 &image.AlbumImagesCount, 298 &image.Width, 299 &image.Height, 300 &audio.DurationMs, 301 &communityID, 302 &serializedMentions, 303 &serializedLinks, 304 &serializedUnfurledLinks, 305 &serializedUnfurledStatusLinks, 306 &command.ID, 307 &command.Value, 308 &command.From, 309 &command.Address, 310 &command.Contract, 311 &command.TransactionHash, 312 &command.CommandState, 313 &command.Signature, 314 &message.Replace, 315 &editedAt, 316 &deleted, 317 &deletedBy, 318 &deletedForMe, 319 &message.RTL, 320 &message.LineCount, 321 &message.ResponseTo, 322 &gapFrom, 323 &gapTo, 324 &contactRequestState, 325 &contactVerificationState, 326 &message.Mentioned, 327 &message.Replied, 328 &discordMessage.Id, 329 &discordMessage.Author.Id, 330 &discordMessage.Type, 331 &discordMessage.Timestamp, 332 &discordMessage.TimestampEdited, 333 &discordMessage.Content, 334 &discordMessage.Reference.MessageId, 335 &discordMessage.Reference.ChannelId, 336 &discordMessage.Author.Name, 337 &discordMessage.Author.Discriminator, 338 &discordMessage.Author.Nickname, 339 &discordMessage.Author.AvatarUrl, 340 &attachment.Id, 341 &attachment.MessageId, 342 &attachment.Url, 343 &attachment.FileName, 344 &attachment.ContentType, 345 "edFrom, 346 "edText, 347 "edParsedText, 348 "edAlbumImages, 349 "edAlbumImagesCount, 350 "edAudioDuration, 351 "edCommunityID, 352 "edID, 353 &ContentType, 354 "edDeleted, 355 "edDeletedForMe, 356 &alias, 357 &identicon, 358 "edDiscordMessage.Id, 359 "edDiscordMessage.Author.Name, 360 "edDiscordMessage.Author.Nickname, 361 "edDiscordMessage.Author.AvatarUrl, 362 &bridgeMessage.BridgeName, 363 &bridgeMessage.UserName, 364 &bridgeMessage.UserAvatar, 365 &bridgeMessage.UserID, 366 &bridgeMessage.Content, 367 &bridgeMessage.MessageID, 368 &bridgeMessage.ParentMessageID, 369 "edBridgeMessage.BridgeName, 370 "edBridgeMessage.UserName, 371 "edBridgeMessage.UserAvatar, 372 "edBridgeMessage.UserID, 373 "edBridgeMessage.Content, 374 } 375 err := row.Scan(append(args, others...)...) 376 if err != nil { 377 return err 378 } 379 380 if editedAt.Valid { 381 message.EditedAt = uint64(editedAt.Int64) 382 } 383 384 if deleted.Valid { 385 message.Deleted = deleted.Bool 386 } 387 388 if deletedBy.Valid { 389 message.DeletedBy = deletedBy.String 390 } 391 392 if deletedForMe.Valid { 393 message.DeletedForMe = deletedForMe.Bool 394 } 395 396 if contactRequestState.Valid { 397 message.ContactRequestState = common.ContactRequestState(contactRequestState.Int64) 398 } 399 400 if contactVerificationState.Valid { 401 message.ContactVerificationState = common.ContactVerificationState(contactVerificationState.Int64) 402 } 403 404 if quotedText.Valid { 405 if quotedDeleted.Bool || quotedDeletedForMe.Bool { 406 message.QuotedMessage = &common.QuotedMessage{ 407 ID: quotedID.String, 408 From: quotedFrom.String, 409 Deleted: quotedDeleted.Bool, 410 DeletedForMe: quotedDeletedForMe.Bool, 411 } 412 } else { 413 message.QuotedMessage = &common.QuotedMessage{ 414 ID: quotedID.String, 415 ContentType: ContentType.Int64, 416 From: quotedFrom.String, 417 Text: quotedText.String, 418 ParsedText: quotedParsedText, 419 AlbumImages: quotedAlbumImages, 420 AlbumImagesCount: quotedAlbumImagesCount.Int64, 421 CommunityID: quotedCommunityID.String, 422 Deleted: quotedDeleted.Bool, 423 } 424 if message.QuotedMessage.ContentType == int64(protobuf.ChatMessage_DISCORD_MESSAGE) { 425 message.QuotedMessage.DiscordMessage = quotedDiscordMessage 426 } 427 if message.QuotedMessage.ContentType == int64(protobuf.ChatMessage_BRIDGE_MESSAGE) { 428 message.QuotedMessage.BridgeMessage = quotedBridgeMessage 429 } 430 } 431 } 432 message.Alias = alias.String 433 message.Identicon = identicon.String 434 435 if gapFrom.Valid && gapTo.Valid { 436 message.GapParameters = &common.GapParameters{ 437 From: uint32(gapFrom.Int64), 438 To: uint32(gapTo.Int64), 439 } 440 } 441 442 if communityID.Valid { 443 message.CommunityID = communityID.String 444 } 445 446 if serializedMentions != nil { 447 err := json.Unmarshal(serializedMentions, &message.Mentions) 448 if err != nil { 449 return err 450 } 451 } 452 453 if serializedLinks != nil { 454 err := json.Unmarshal(serializedLinks, &message.Links) 455 if err != nil { 456 return err 457 } 458 } 459 460 if serializedUnfurledLinks != nil { 461 err = json.Unmarshal(serializedUnfurledLinks, &message.UnfurledLinks) 462 if err != nil { 463 return err 464 } 465 } 466 467 if serializedUnfurledStatusLinks != nil { 468 // use proto.Marshal, because json.Marshal doesn't support `oneof` fields 469 var links protobuf.UnfurledStatusLinks 470 err = proto.Unmarshal(serializedUnfurledStatusLinks, &links) 471 if err != nil { 472 return err 473 } 474 message.UnfurledStatusLinks = &links 475 } 476 477 if attachment.Id != "" { 478 discordMessage.Attachments = append(discordMessage.Attachments, attachment) 479 } 480 481 switch message.ContentType { 482 case protobuf.ChatMessage_STICKER: 483 message.Payload = &protobuf.ChatMessage_Sticker{Sticker: sticker} 484 485 case protobuf.ChatMessage_AUDIO: 486 message.Payload = &protobuf.ChatMessage_Audio{Audio: audio} 487 488 case protobuf.ChatMessage_TRANSACTION_COMMAND: 489 message.CommandParameters = command 490 491 case protobuf.ChatMessage_IMAGE: 492 message.Payload = &protobuf.ChatMessage_Image{Image: image} 493 494 case protobuf.ChatMessage_DISCORD_MESSAGE: 495 message.Payload = &protobuf.ChatMessage_DiscordMessage{ 496 DiscordMessage: discordMessage, 497 } 498 499 case protobuf.ChatMessage_BRIDGE_MESSAGE: 500 message.Payload = &protobuf.ChatMessage_BridgeMessage{ 501 BridgeMessage: bridgeMessage, 502 } 503 } 504 505 return nil 506 } 507 508 func (db sqlitePersistence) tableUserMessagesAllValues(message *common.Message) ([]interface{}, error) { 509 var gapFrom, gapTo uint32 510 511 var albumImages []byte 512 if message.QuotedMessage != nil { 513 albumImages = []byte(message.QuotedMessage.AlbumImages) 514 } 515 516 sticker := message.GetSticker() 517 if sticker == nil { 518 sticker = &protobuf.StickerMessage{} 519 } 520 521 image := message.GetImage() 522 if image == nil { 523 image = &protobuf.ImageMessage{} 524 } 525 526 audio := message.GetAudio() 527 if audio == nil { 528 audio = &protobuf.AudioMessage{} 529 } 530 531 command := message.CommandParameters 532 if command == nil { 533 command = &common.CommandParameters{} 534 } 535 536 discordMessage := message.GetDiscordMessage() 537 if discordMessage == nil { 538 discordMessage = &protobuf.DiscordMessage{ 539 Author: &protobuf.DiscordMessageAuthor{}, 540 Reference: &protobuf.DiscordMessageReference{}, 541 Attachments: make([]*protobuf.DiscordMessageAttachment, 0), 542 } 543 } 544 545 if message.GapParameters != nil { 546 gapFrom = message.GapParameters.From 547 gapTo = message.GapParameters.To 548 } 549 550 var serializedMentions []byte 551 var err error 552 if len(message.Mentions) != 0 { 553 serializedMentions, err = json.Marshal(message.Mentions) 554 if err != nil { 555 return nil, err 556 } 557 } 558 559 var serializedLinks []byte 560 if len(message.Links) != 0 { 561 serializedLinks, err = json.Marshal(message.Links) 562 if err != nil { 563 return nil, err 564 } 565 } 566 567 var serializedUnfurledLinks []byte 568 if links := message.GetUnfurledLinks(); links != nil { 569 serializedUnfurledLinks, err = json.Marshal(links) 570 if err != nil { 571 return nil, err 572 } 573 } 574 575 var serializedUnfurledStatusLinks []byte 576 if links := message.GetUnfurledStatusLinks(); links != nil { 577 // use proto.Marshal, because json.Marshal doesn't support `oneof` fields 578 serializedUnfurledStatusLinks, err = proto.Marshal(links) 579 if err != nil { 580 return nil, err 581 } 582 } 583 584 return []interface{}{ 585 message.ID, 586 message.WhisperTimestamp, 587 message.From, // source in table 588 message.Text, 589 message.ContentType, 590 message.Alias, 591 message.Timestamp, 592 message.ChatId, 593 message.LocalChatID, 594 message.MessageType, 595 message.Clock, 596 message.Seen, 597 message.OutgoingStatus, 598 message.ParsedText, 599 sticker.Pack, 600 sticker.Hash, 601 image.Payload, 602 image.Format, 603 image.AlbumId, 604 albumImages, 605 image.AlbumImagesCount, 606 image.Width, 607 image.Height, 608 message.Base64Image, 609 audio.Payload, 610 audio.Type, 611 audio.DurationMs, 612 message.Base64Audio, 613 message.CommunityID, 614 serializedMentions, 615 serializedLinks, 616 serializedUnfurledLinks, 617 serializedUnfurledStatusLinks, 618 command.ID, 619 command.Value, 620 command.From, 621 command.Address, 622 command.Contract, 623 command.TransactionHash, 624 command.CommandState, 625 command.Signature, 626 message.Replace, 627 int64(message.EditedAt), 628 message.Deleted, 629 message.DeletedBy, 630 message.DeletedForMe, 631 message.RTL, 632 message.LineCount, 633 message.ResponseTo, 634 gapFrom, 635 gapTo, 636 message.ContactRequestState, 637 message.ContactVerificationState, 638 message.Mentioned, 639 message.Replied, 640 discordMessage.Id, 641 }, nil 642 } 643 644 func (db sqlitePersistence) messageByID(tx *sql.Tx, id string) (*common.Message, error) { 645 var err error 646 if tx == nil { 647 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 648 if err != nil { 649 return nil, err 650 } 651 defer func() { 652 if err == nil { 653 err = tx.Commit() 654 return 655 } 656 // don't shadow original error 657 _ = tx.Rollback() 658 }() 659 } 660 661 query := db.buildMessagesQuery("WHERE m1.id = ?") 662 rows, err := tx.Query(query, id) 663 if err != nil { 664 return nil, err 665 } 666 defer rows.Close() 667 return getMessageFromScanRows(db, rows) 668 } 669 670 func (db sqlitePersistence) albumMessages(chatID, albumID string) ([]*common.Message, error) { 671 if albumID == "" { 672 return nil, nil 673 } 674 query := db.buildMessagesQuery("WHERE m1.album_id = ? and m1.local_chat_id = ?") 675 rows, err := db.db.Query(query, albumID, chatID) 676 if err != nil { 677 return nil, err 678 } 679 defer rows.Close() 680 return getMessagesFromScanRows(db, rows, false) 681 } 682 683 func (db sqlitePersistence) MessageByCommandID(chatID, id string) (*common.Message, error) { 684 685 where := `WHERE 686 m1.command_id = ? 687 AND 688 m1.local_chat_id = ? 689 ORDER BY m1.clock_value DESC 690 LIMIT 1` 691 query := db.buildMessagesQuery(where) 692 rows, err := db.db.Query(query, id, chatID) 693 if err != nil { 694 return nil, err 695 } 696 defer rows.Close() 697 return getMessageFromScanRows(db, rows) 698 } 699 700 func (db sqlitePersistence) MessageByID(id string) (*common.Message, error) { 701 return db.messageByID(nil, id) 702 } 703 704 func (db sqlitePersistence) AlbumMessages(chatID, albumID string) ([]*common.Message, error) { 705 return db.albumMessages(chatID, albumID) 706 } 707 708 func (db sqlitePersistence) MessagesExist(ids []string) (map[string]bool, error) { 709 result := make(map[string]bool) 710 if len(ids) == 0 { 711 return result, nil 712 } 713 714 idsArgs := make([]interface{}, 0, len(ids)) 715 for _, id := range ids { 716 idsArgs = append(idsArgs, id) 717 } 718 719 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 720 query := "SELECT id FROM user_messages WHERE id IN (" + inVector + ")" // nolint: gosec 721 rows, err := db.db.Query(query, idsArgs...) 722 if err != nil { 723 return nil, err 724 } 725 defer rows.Close() 726 727 for rows.Next() { 728 var id string 729 err := rows.Scan(&id) 730 if err != nil { 731 return nil, err 732 } 733 result[id] = true 734 } 735 736 return result, nil 737 } 738 739 func (db sqlitePersistence) MessagesByIDs(ids []string) ([]*common.Message, error) { 740 if len(ids) == 0 { 741 return nil, nil 742 } 743 744 idsArgs := make([]interface{}, 0, len(ids)) 745 for _, id := range ids { 746 idsArgs = append(idsArgs, id) 747 } 748 749 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 750 751 // nolint: gosec 752 where := fmt.Sprintf("WHERE NOT(m1.hide) AND m1.id IN (%s)", inVector) 753 query := db.buildMessagesQuery(where) 754 rows, err := db.db.Query(query, idsArgs...) 755 if err != nil { 756 return nil, err 757 } 758 defer rows.Close() 759 return getMessagesFromScanRows(db, rows, false) 760 } 761 762 func (db sqlitePersistence) MessagesByResponseTo(responseTo string) ([]*common.Message, error) { 763 where := "WHERE m1.response_to = ?" 764 query := db.buildMessagesQuery(where) 765 rows, err := db.db.Query(query, responseTo) 766 if err != nil { 767 return nil, err 768 } 769 defer rows.Close() 770 return getMessagesFromScanRows(db, rows, false) 771 } 772 773 // MessageByChatID returns all messages for a given chatID in descending order. 774 // Ordering is accomplished using two concatenated values: ClockValue and ID. 775 // These two values are also used to compose a cursor which is returned to the result. 776 func (db sqlitePersistence) MessageByChatID(chatID string, currCursor string, limit int) ([]*common.Message, string, error) { 777 cursorWhere := "" 778 if currCursor != "" { 779 cursorWhere = "AND cursor <= ?" //nolint: goconst 780 } 781 args := []interface{}{chatID} 782 if currCursor != "" { 783 args = append(args, currCursor) 784 } 785 // Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning 786 // concatenated with message ID. Results are sorted using this new column. 787 // This new column values can also be returned as a cursor for subsequent requests. 788 where := fmt.Sprintf(` 789 WHERE 790 NOT(m1.hide) AND m1.local_chat_id = ? %s 791 ORDER BY cursor DESC 792 LIMIT ?`, cursorWhere) 793 794 query := db.buildMessagesQueryWithAdditionalFields(cursorField, where) 795 rows, err := db.db.Query( 796 query, 797 append(args, limit+1)..., // take one more to figure our whether a cursor should be returned 798 ) 799 if err != nil { 800 return nil, "", err 801 } 802 defer rows.Close() 803 804 result, cursors, err := getMessagesAndCursorsFromScanRows(db, rows) 805 if err != nil { 806 return nil, "", err 807 } 808 809 var newCursor string 810 if len(result) > limit { 811 newCursor = cursors[limit] 812 result = result[:limit] 813 } 814 return result, newCursor, nil 815 } 816 817 func (db sqlitePersistence) FirstUnseenMessageID(chatID string) (string, error) { 818 var id string 819 err := db.db.QueryRow(` 820 SELECT 821 id 822 FROM 823 user_messages m1 824 WHERE 825 m1.local_chat_id = ? 826 AND NOT(m1.seen) AND NOT(m1.hide) AND NOT(m1.deleted) AND NOT(m1.deleted_for_me) 827 ORDER BY m1.clock_value ASC 828 LIMIT 1`, 829 chatID).Scan(&id) 830 831 if err == sql.ErrNoRows { 832 return "", nil 833 } 834 if err != nil { 835 return "", err 836 } 837 return id, nil 838 } 839 840 // Get last chat message that is not hide or deleted or deleted_for_me 841 func (db sqlitePersistence) LatestMessageByChatID(chatID string) ([]*common.Message, error) { 842 args := []interface{}{chatID} 843 where := `WHERE 844 NOT(m1.hide) AND NOT(m1.deleted) AND NOT(m1.deleted_for_me) AND m1.local_chat_id = ? 845 ORDER BY cursor DESC 846 LIMIT ?` 847 848 query := db.buildMessagesQueryWithAdditionalFields(cursorField, where) 849 850 rows, err := db.db.Query( 851 query, 852 append(args, 2)..., // take one more to figure our whether a cursor should be returned 853 ) 854 if err != nil { 855 return nil, err 856 } 857 defer rows.Close() 858 859 result, _, err := getMessagesAndCursorsFromScanRows(db, rows) 860 if err != nil { 861 return nil, err 862 } 863 864 if len(result) > 1 { 865 result = result[:1] 866 } 867 return result, nil 868 } 869 870 func (db sqlitePersistence) latestIncomingMessageClock(chatID string) (uint64, error) { 871 var clock uint64 872 err := db.db.QueryRow( 873 fmt.Sprintf( 874 ` 875 SELECT 876 clock_value 877 FROM 878 user_messages m1 879 WHERE 880 m1.local_chat_id = ? AND m1.outgoing_status = '' 881 %s DESC 882 LIMIT 1 883 `, cursor), 884 chatID).Scan(&clock) 885 if err != nil { 886 return 0, err 887 } 888 return clock, nil 889 } 890 891 func (db sqlitePersistence) PendingContactRequests(currCursor string, limit int) ([]*common.Message, string, error) { 892 cursorWhere := "" 893 if currCursor != "" { 894 cursorWhere = "AND cursor <= ?" //nolint: goconst 895 } 896 args := []interface{}{protobuf.ChatMessage_CONTACT_REQUEST} 897 if currCursor != "" { 898 args = append(args, currCursor) 899 } 900 // Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning 901 // concatenated with message ID. Results are sorted using this new column. 902 // This new column values can also be returned as a cursor for subsequent requests. 903 where := fmt.Sprintf(` 904 WHERE 905 NOT(m1.hide) AND NOT(m1.seen) AND m1.content_type = ? %s 906 ORDER BY cursor DESC 907 LIMIT ?`, cursorWhere) 908 909 query := db.buildMessagesQueryWithAdditionalFields(cursorField, where) 910 rows, err := db.db.Query( 911 query, 912 append(args, limit+1)..., // take one more to figure our whether a cursor should be returned 913 ) 914 if err != nil { 915 return nil, "", err 916 } 917 defer rows.Close() 918 919 result, cursors, err := getMessagesAndCursorsFromScanRows(db, rows) 920 if err != nil { 921 return nil, "", err 922 } 923 924 var newCursor string 925 if len(result) > limit { 926 newCursor = cursors[limit] 927 result = result[:limit] 928 } 929 return result, newCursor, nil 930 } 931 932 func (db sqlitePersistence) LatestPendingContactRequestIDForContact(contactID string) (string, error) { 933 var id string 934 err := db.db.QueryRow( 935 fmt.Sprintf( 936 ` 937 SELECT 938 id 939 FROM 940 user_messages m1 941 WHERE 942 m1.local_chat_id = ? AND m1.content_type = ? 943 ORDER BY %s DESC 944 LIMIT 1 945 `, cursor), 946 contactID, protobuf.ChatMessage_CONTACT_REQUEST).Scan(&id) 947 if err == sql.ErrNoRows { 948 return "", nil 949 } 950 951 if err != nil { 952 return "", err 953 } 954 return id, nil 955 } 956 957 func (db sqlitePersistence) LatestContactRequests() ([]LatestContactRequest, error) { 958 res := make([]LatestContactRequest, 0) 959 rows, err := db.db.Query( 960 fmt.Sprintf( 961 ` 962 SELECT 963 id, contact_request_state, local_chat_id 964 FROM 965 user_messages m1 966 WHERE 967 m1.content_type = ? 968 ORDER BY %s DESC 969 LIMIT 200 970 `, cursor), protobuf.ChatMessage_CONTACT_REQUEST) 971 972 if err != nil { 973 return res, err 974 } 975 976 defer rows.Close() 977 978 for rows.Next() { 979 var id string 980 var contactRequestState sql.NullInt64 981 var localChatID string 982 err := rows.Scan(&id, &contactRequestState, &localChatID) 983 984 if err != nil { 985 return nil, err 986 } 987 res = append(res, LatestContactRequest{ 988 MessageID: id, 989 ContactRequestState: common.ContactRequestState(contactRequestState.Int64), 990 ContactID: localChatID, 991 }) 992 } 993 994 return res, nil 995 } 996 997 // AllMessageByChatIDWhichMatchPattern returns all messages which match the search 998 // term, for a given chatID in descending order. 999 // Ordering is accomplished using two concatenated values: ClockValue and ID. 1000 // These two values are also used to compose a cursor which is returned to the result. 1001 func (db sqlitePersistence) AllMessageByChatIDWhichMatchTerm(chatID string, searchTerm string, caseSensitive bool) ([]*common.Message, error) { 1002 if searchTerm == "" { 1003 return nil, fmt.Errorf("empty search term") 1004 } 1005 1006 searchCond := "" 1007 if caseSensitive { 1008 searchCond = "AND " + caseSensitiveSearchCond 1009 } else { 1010 searchCond = "AND " + caseInsensitiveSearchCond 1011 } 1012 1013 where := fmt.Sprintf(` 1014 WHERE 1015 NOT(m1.hide) AND m1.local_chat_id = ? %s 1016 ORDER BY cursor DESC`, searchCond) 1017 1018 query := db.buildMessagesQueryWithAdditionalFields(cursorField, where) 1019 rows, err := db.db.Query( 1020 query, 1021 chatID, 1022 searchTerm, 1023 searchTerm, 1024 searchTerm, 1025 ) 1026 1027 if err != nil { 1028 return nil, err 1029 } 1030 defer rows.Close() 1031 return getMessagesFromScanRows(db, rows, true) 1032 } 1033 1034 // AllMessagesFromChatsAndCommunitiesWhichMatchTerm returns all messages which match the search 1035 // term, if they belong to either any chat from the chatIds array or any channel of any community 1036 // from communityIds array. 1037 // Ordering is accomplished using two concatenated values: ClockValue and ID. 1038 // These two values are also used to compose a cursor which is returned to the result. 1039 func (db sqlitePersistence) AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communityIds []string, chatIds []string, searchTerm string, caseSensitive bool) ([]*common.Message, error) { 1040 if searchTerm == "" { 1041 return nil, fmt.Errorf("empty search term") 1042 } 1043 1044 chatsCond := "" 1045 if len(chatIds) > 0 { 1046 inVector := strings.Repeat("?, ", len(chatIds)-1) + "?" 1047 chatsCond = `m1.local_chat_id IN (%s)` 1048 chatsCond = fmt.Sprintf(chatsCond, inVector) 1049 } 1050 1051 communitiesCond := "" 1052 if len(communityIds) > 0 { 1053 inVector := strings.Repeat("?, ", len(communityIds)-1) + "?" 1054 communitiesCond = `m1.local_chat_id IN (SELECT id FROM chats WHERE community_id IN (%s))` 1055 communitiesCond = fmt.Sprintf(communitiesCond, inVector) 1056 } 1057 1058 searchCond := "" 1059 if caseSensitive { 1060 searchCond = caseSensitiveSearchCond 1061 } else { 1062 searchCond = caseInsensitiveSearchCond 1063 } 1064 1065 finalCond := "AND %s AND %s" 1066 if len(communityIds) > 0 && len(chatIds) > 0 { 1067 finalCond = "AND (%s OR %s) AND %s" 1068 finalCond = fmt.Sprintf(finalCond, chatsCond, communitiesCond, searchCond) 1069 } else if len(chatIds) > 0 { 1070 finalCond = fmt.Sprintf(finalCond, chatsCond, searchCond) 1071 } else if len(communityIds) > 0 { 1072 finalCond = fmt.Sprintf(finalCond, communitiesCond, searchCond) 1073 } else { 1074 return nil, fmt.Errorf("you must specify either community ids or chat ids or both") 1075 } 1076 1077 var parameters []string 1078 parameters = append(parameters, chatIds...) 1079 parameters = append(parameters, communityIds...) 1080 parameters = append(parameters, searchTerm) 1081 parameters = append(parameters, searchTerm) 1082 parameters = append(parameters, searchTerm) 1083 1084 idsArgs := make([]interface{}, 0, len(parameters)) 1085 for _, param := range parameters { 1086 idsArgs = append(idsArgs, param) 1087 } 1088 1089 where := fmt.Sprintf(` 1090 WHERE 1091 NOT(m1.hide) %s 1092 ORDER BY cursor DESC`, finalCond) 1093 1094 finalQuery := db.buildMessagesQueryWithAdditionalFields(cursorField, where) 1095 rows, err := db.db.Query(finalQuery, idsArgs...) 1096 1097 if err != nil { 1098 return nil, err 1099 } 1100 defer rows.Close() 1101 return getMessagesFromScanRows(db, rows, true) 1102 } 1103 1104 func (db sqlitePersistence) AllChatIDsByCommunity(tx *sql.Tx, communityID string) ([]string, error) { 1105 var err error 1106 var rows *sql.Rows 1107 query := "SELECT id FROM chats WHERE community_id = ?" 1108 if tx == nil { 1109 rows, err = db.db.Query(query, communityID) 1110 } else { 1111 rows, err = tx.Query(query, communityID) 1112 } 1113 1114 if err != nil { 1115 return nil, err 1116 } 1117 defer rows.Close() 1118 1119 var rst []string 1120 1121 for rows.Next() { 1122 var chatID string 1123 err = rows.Scan(&chatID) 1124 if err != nil { 1125 return nil, err 1126 } 1127 rst = append(rst, chatID) 1128 } 1129 1130 return rst, nil 1131 } 1132 1133 func (db sqlitePersistence) CountActiveChattersInCommunity(communityID string, activeAfterTimestamp int64) (uint, error) { 1134 var activeChattersCount uint 1135 err := db.db.QueryRow( 1136 ` 1137 SELECT COUNT(DISTINCT source) 1138 FROM user_messages 1139 JOIN chats ON user_messages.local_chat_id = chats.id 1140 WHERE chats.community_id = ? 1141 AND user_messages.timestamp >= ? 1142 `, communityID, activeAfterTimestamp).Scan(&activeChattersCount) 1143 if err == sql.ErrNoRows { 1144 return 0, nil 1145 } 1146 if err != nil { 1147 return 0, err 1148 } 1149 1150 return activeChattersCount, nil 1151 } 1152 1153 // PinnedMessageByChatID returns all pinned messages for a given chatID in descending order. 1154 // Ordering is accomplished using two concatenated values: ClockValue and ID. 1155 // These two values are also used to compose a cursor which is returned to the result. 1156 func (db sqlitePersistence) PinnedMessageByChatIDs(chatIDs []string, currCursor string, limit int) ([]*common.PinnedMessage, string, error) { 1157 cursorWhere := "" 1158 if currCursor != "" { 1159 cursorWhere = "AND cursor <= ?" //nolint: goconst 1160 } 1161 args := make([]interface{}, len(chatIDs)) 1162 for i, v := range chatIDs { 1163 args[i] = v 1164 } 1165 if currCursor != "" { 1166 args = append(args, currCursor) 1167 } 1168 1169 limitStr := "" 1170 if limit > -1 { 1171 args = append(args, limit+1) // take one more to figure our whether a cursor should be returned 1172 } 1173 // Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning 1174 // concatenated with message ID. Results are sorted using this new column. 1175 // This new column values can also be returned as a cursor for subsequent requests. 1176 allFields := db.tableUserMessagesAllFieldsJoin() 1177 rows, err := db.db.Query( 1178 fmt.Sprintf(` 1179 SELECT 1180 %s, 1181 pm.clock_value as pinnedAt, 1182 pm.pinned_by as pinnedBy, 1183 %s 1184 FROM 1185 pin_messages pm 1186 JOIN 1187 user_messages m1 1188 ON 1189 pm.message_id = m1.id 1190 LEFT JOIN 1191 user_messages m2 1192 ON 1193 m1.response_to = m2.id 1194 LEFT JOIN 1195 contacts c 1196 ON 1197 m1.source = c.id 1198 1199 LEFT JOIN 1200 discord_messages dm 1201 ON 1202 m1.discord_message_id = dm.id 1203 1204 LEFT JOIN 1205 discord_message_authors dm_author 1206 ON 1207 dm.author_id = dm_author.id 1208 1209 LEFT JOIN 1210 discord_message_attachments dm_attachment 1211 ON 1212 dm.id = dm_attachment.discord_message_id 1213 1214 LEFT JOIN 1215 discord_messages m2_dm 1216 ON 1217 m2.discord_message_id = m2_dm.id 1218 1219 LEFT JOIN 1220 discord_message_authors m2_dm_author 1221 ON 1222 m2_dm.author_id = m2_dm_author.id 1223 1224 LEFT JOIN bridge_messages bm 1225 ON m1.id = bm.user_messages_id 1226 1227 LEFT JOIN bridge_messages bm_response 1228 ON m2.id = bm_response.user_messages_id 1229 1230 WHERE 1231 pm.pinned = 1 1232 AND NOT(m1.hide) AND m1.local_chat_id IN %s %s 1233 ORDER BY cursor DESC 1234 %s 1235 `, allFields, cursorField, "(?"+strings.Repeat(",?", len(chatIDs)-1)+")", cursorWhere, limitStr), 1236 args..., // take one more to figure our whether a cursor should be returned 1237 ) 1238 if err != nil { 1239 return nil, "", err 1240 } 1241 defer rows.Close() 1242 1243 result, cursors, err := getPinnedMessagesAndCursorsFromScanRows(db, rows) 1244 if err != nil { 1245 return nil, "", err 1246 } 1247 1248 var newCursor string 1249 1250 if limit > -1 && len(result) > limit && cursors != nil { 1251 newCursor = cursors[limit] 1252 result = result[:limit] 1253 } 1254 return result, newCursor, nil 1255 } 1256 1257 func (db sqlitePersistence) PinnedMessageByChatID(chatID string, currCursor string, limit int) ([]*common.PinnedMessage, string, error) { 1258 return db.PinnedMessageByChatIDs([]string{chatID}, currCursor, limit) 1259 } 1260 1261 // MessageByChatIDs returns all messages for a given chatIDs in descending order. 1262 // Ordering is accomplished using two concatenated values: ClockValue and ID. 1263 // These two values are also used to compose a cursor which is returned to the result. 1264 func (db sqlitePersistence) MessageByChatIDs(chatIDs []string, currCursor string, limit int) ([]*common.Message, string, error) { 1265 cursorWhere := "" 1266 if currCursor != "" { 1267 cursorWhere = "AND cursor <= ?" //nolint: goconst 1268 } 1269 args := make([]interface{}, len(chatIDs)) 1270 for i, v := range chatIDs { 1271 args[i] = v 1272 } 1273 if currCursor != "" { 1274 args = append(args, currCursor) 1275 } 1276 // Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning 1277 // concatenated with message ID. Results are sorted using this new column. 1278 // This new column values can also be returned as a cursor for subsequent requests. 1279 where := fmt.Sprintf(` 1280 WHERE 1281 NOT(m1.hide) AND m1.local_chat_id IN %s %s 1282 ORDER BY cursor DESC 1283 LIMIT ? 1284 `, "(?"+strings.Repeat(",?", len(chatIDs)-1)+")", cursorWhere) 1285 query := db.buildMessagesQueryWithAdditionalFields(cursorField, where) 1286 rows, err := db.db.Query( 1287 query, 1288 append(args, limit+1)..., // take one more to figure our whether a cursor should be returned 1289 ) 1290 if err != nil { 1291 return nil, "", err 1292 } 1293 defer rows.Close() 1294 1295 result, cursors, err := getMessagesAndCursorsFromScanRows(db, rows) 1296 if err != nil { 1297 return nil, "", err 1298 } 1299 1300 var newCursor string 1301 if len(result) > limit { 1302 newCursor = cursors[limit] 1303 result = result[:limit] 1304 } 1305 return result, newCursor, nil 1306 } 1307 1308 func (db sqlitePersistence) OldestMessageWhisperTimestampByChatID(chatID string) (timestamp uint64, hasAnyMessage bool, err error) { 1309 var whisperTimestamp uint64 1310 err = db.db.QueryRow( 1311 ` 1312 SELECT 1313 whisper_timestamp 1314 FROM 1315 user_messages m1 1316 WHERE 1317 m1.local_chat_id = ? 1318 ORDER BY substr('0000000000000000000000000000000000000000000000000000000000000000' || m1.clock_value, -64, 64) || m1.id ASC 1319 LIMIT 1 1320 `, chatID).Scan(&whisperTimestamp) 1321 if err == sql.ErrNoRows { 1322 return 0, false, nil 1323 } 1324 if err != nil { 1325 return 0, false, err 1326 } 1327 return whisperTimestamp, true, nil 1328 } 1329 1330 // EmojiReactionsByChatID returns the emoji reactions for the queried messages, up to a maximum of 100, as it's a potentially unbound number. 1331 // NOTE: This is not completely accurate, as the messages in the database might have change since the last call to `MessageByChatID`. 1332 func (db sqlitePersistence) EmojiReactionsByChatID(chatID string, currCursor string, limit int) ([]*EmojiReaction, error) { 1333 cursorWhere := "" 1334 if currCursor != "" { 1335 cursorWhere = fmt.Sprintf("AND %s <= ?", cursor) //nolint: goconst 1336 } 1337 args := []interface{}{chatID, chatID} 1338 if currCursor != "" { 1339 args = append(args, currCursor) 1340 } 1341 args = append(args, limit) 1342 // NOTE: We match against local_chat_id for security reasons. 1343 // As a user could potentially send an emoji reaction for a one to 1344 // one/group chat that has no access to. 1345 // We also limit the number of emoji to a reasonable number (1000) 1346 // for now, as we don't want the client to choke on this. 1347 // The issue is that your own emoji might not be returned in such cases, 1348 // allowing the user to react to a post multiple times. 1349 // Jakubgs: Returning the whole list seems like a real overkill. 1350 // This will get very heavy in threads that have loads of reactions on loads of messages. 1351 // A more sensible response would just include a count and a bool telling you if you are in the list. 1352 // nolint: gosec 1353 query := fmt.Sprintf(` 1354 SELECT 1355 e.clock_value, 1356 e.source, 1357 e.emoji_id, 1358 e.message_id, 1359 e.chat_id, 1360 e.local_chat_id, 1361 e.retracted 1362 FROM 1363 emoji_reactions e 1364 WHERE NOT(e.retracted) 1365 AND 1366 e.local_chat_id = ? 1367 AND 1368 e.message_id IN 1369 (SELECT id FROM user_messages m1 WHERE NOT(m1.hide) AND m1.local_chat_id = ? %s 1370 ORDER BY %s DESC LIMIT ?) 1371 LIMIT 1000 1372 `, cursorWhere, cursor) 1373 1374 rows, err := db.db.Query( 1375 query, 1376 args..., 1377 ) 1378 if err != nil { 1379 return nil, err 1380 } 1381 defer rows.Close() 1382 1383 var result []*EmojiReaction 1384 for rows.Next() { 1385 emojiReaction := NewEmojiReaction() 1386 err := rows.Scan(&emojiReaction.Clock, 1387 &emojiReaction.From, 1388 &emojiReaction.Type, 1389 &emojiReaction.MessageId, 1390 &emojiReaction.ChatId, 1391 &emojiReaction.LocalChatID, 1392 &emojiReaction.Retracted) 1393 if err != nil { 1394 return nil, err 1395 } 1396 1397 result = append(result, emojiReaction) 1398 } 1399 1400 return result, nil 1401 } 1402 1403 // EmojiReactionsByChatIDMessageID returns the emoji reactions for the queried message. 1404 func (db sqlitePersistence) EmojiReactionsByChatIDMessageID(chatID string, messageID string) ([]*EmojiReaction, error) { 1405 1406 args := []interface{}{chatID, messageID} 1407 query := `SELECT 1408 e.clock_value, 1409 e.source, 1410 e.emoji_id, 1411 e.message_id, 1412 e.chat_id, 1413 e.local_chat_id, 1414 e.retracted 1415 FROM 1416 emoji_reactions e 1417 WHERE NOT(e.retracted) 1418 AND 1419 e.local_chat_id = ? 1420 AND 1421 e.message_id = ? 1422 LIMIT 1000` 1423 1424 rows, err := db.db.Query( 1425 query, 1426 args..., 1427 ) 1428 if err != nil { 1429 return nil, err 1430 } 1431 defer rows.Close() 1432 1433 var result []*EmojiReaction 1434 for rows.Next() { 1435 emojiReaction := NewEmojiReaction() 1436 err := rows.Scan(&emojiReaction.Clock, 1437 &emojiReaction.From, 1438 &emojiReaction.Type, 1439 &emojiReaction.MessageId, 1440 &emojiReaction.ChatId, 1441 &emojiReaction.LocalChatID, 1442 &emojiReaction.Retracted) 1443 if err != nil { 1444 return nil, err 1445 } 1446 1447 result = append(result, emojiReaction) 1448 } 1449 1450 return result, nil 1451 } 1452 1453 // EmojiReactionsByChatIDs returns the emoji reactions for the queried messages, up to a maximum of 100, as it's a potentially unbound number. 1454 // NOTE: This is not completely accurate, as the messages in the database might have change since the last call to `MessageByChatID`. 1455 func (db sqlitePersistence) EmojiReactionsByChatIDs(chatIDs []string, currCursor string, limit int) ([]*EmojiReaction, error) { 1456 cursorWhere := "" 1457 if currCursor != "" { 1458 cursorWhere = fmt.Sprintf("AND %s <= ?", cursor) //nolint: goconst 1459 } 1460 chatsLen := len(chatIDs) 1461 args := make([]interface{}, chatsLen*2) 1462 for i, v := range chatIDs { 1463 args[i] = v 1464 } 1465 for i, v := range chatIDs { 1466 args[chatsLen+i] = v 1467 } 1468 if currCursor != "" { 1469 args = append(args, currCursor) 1470 } 1471 args = append(args, limit) 1472 // NOTE: We match against local_chat_id for security reasons. 1473 // As a user could potentially send an emoji reaction for a one to 1474 // one/group chat that has no access to. 1475 // We also limit the number of emoji to a reasonable number (1000) 1476 // for now, as we don't want the client to choke on this. 1477 // The issue is that your own emoji might not be returned in such cases, 1478 // allowing the user to react to a post multiple times. 1479 // Jakubgs: Returning the whole list seems like a real overkill. 1480 // This will get very heavy in threads that have loads of reactions on loads of messages. 1481 // A more sensible response would just include a count and a bool telling you if you are in the list. 1482 // nolint: gosec 1483 query := fmt.Sprintf(` 1484 SELECT 1485 e.clock_value, 1486 e.source, 1487 e.emoji_id, 1488 e.message_id, 1489 e.chat_id, 1490 e.local_chat_id, 1491 e.retracted 1492 FROM 1493 emoji_reactions e 1494 WHERE NOT(e.retracted) 1495 AND 1496 e.local_chat_id IN %s 1497 AND 1498 e.message_id IN 1499 (SELECT id FROM user_messages m WHERE NOT(m.hide) AND m.local_chat_id IN %s %s 1500 ORDER BY %s DESC LIMIT ?) 1501 LIMIT 1000 1502 `, "(?"+strings.Repeat(",?", chatsLen-1)+")", "(?"+strings.Repeat(",?", chatsLen-1)+")", cursorWhere, cursor) 1503 1504 rows, err := db.db.Query( 1505 query, 1506 args..., 1507 ) 1508 if err != nil { 1509 return nil, err 1510 } 1511 defer rows.Close() 1512 1513 var result []*EmojiReaction 1514 for rows.Next() { 1515 emojiReaction := NewEmojiReaction() 1516 err := rows.Scan(&emojiReaction.Clock, 1517 &emojiReaction.From, 1518 &emojiReaction.Type, 1519 &emojiReaction.MessageId, 1520 &emojiReaction.ChatId, 1521 &emojiReaction.LocalChatID, 1522 &emojiReaction.Retracted) 1523 if err != nil { 1524 return nil, err 1525 } 1526 1527 result = append(result, emojiReaction) 1528 } 1529 1530 return result, nil 1531 } 1532 func (db sqlitePersistence) SaveMessages(messages []*common.Message) (err error) { 1533 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1534 if err != nil { 1535 return 1536 } 1537 defer func() { 1538 if err == nil { 1539 err = tx.Commit() 1540 return 1541 } 1542 // don't shadow original error 1543 _ = tx.Rollback() 1544 }() 1545 1546 allFields := db.tableUserMessagesAllFields() 1547 valuesVector := strings.Repeat("?, ", db.tableUserMessagesAllFieldsCount()-1) + "?" 1548 query := "INSERT INTO user_messages(" + allFields + ") VALUES (" + valuesVector + ")" // nolint: gosec 1549 stmt, err := tx.Prepare(query) 1550 if err != nil { 1551 return 1552 } 1553 1554 for _, msg := range messages { 1555 var allValues []interface{} 1556 allValues, err = db.tableUserMessagesAllValues(msg) 1557 if err != nil { 1558 return 1559 } 1560 1561 _, err = stmt.Exec(allValues...) 1562 if err != nil { 1563 return 1564 } 1565 1566 if msg.ContentType == protobuf.ChatMessage_BRIDGE_MESSAGE { 1567 // check updates first 1568 var hasMessage bool 1569 hasMessage, err = db.bridgeMessageExists(tx, msg.GetBridgeMessage().MessageID) 1570 if err != nil { 1571 return 1572 } 1573 if hasMessage { 1574 // bridge message exists, this is edit 1575 err = db.updateBridgeMessageContent(tx, msg.GetBridgeMessage().MessageID, msg.GetBridgeMessage().Content) 1576 return 1577 } 1578 1579 err = db.saveBridgeMessage(tx, msg.GetBridgeMessage(), msg.ID) 1580 if err != nil { 1581 return 1582 } 1583 // handle replies 1584 err = db.findAndUpdateReplies(tx, msg.GetBridgeMessage().MessageID, msg.ID) 1585 if err != nil { 1586 return 1587 } 1588 parentMessageID := msg.GetBridgeMessage().ParentMessageID 1589 if parentMessageID != "" { 1590 err = db.findAndUpdateRepliedTo(tx, parentMessageID, msg.ID) 1591 if err != nil { 1592 return 1593 } 1594 } 1595 1596 } 1597 } 1598 return 1599 } 1600 1601 type insertPinMessagesQueries struct { 1602 selectStmt string 1603 insertStmt *sql.Stmt 1604 updateStmt *sql.Stmt 1605 transaction *sql.Tx 1606 } 1607 1608 func (db sqlitePersistence) buildPinMessageQueries() (*insertPinMessagesQueries, error) { 1609 tx, err := db.db.BeginTx(context.Background(), nil) 1610 if err != nil { 1611 return nil, err 1612 } 1613 1614 queries := &insertPinMessagesQueries{} 1615 // select 1616 queries.selectStmt = "SELECT clock_value FROM pin_messages WHERE id = ?" 1617 1618 // insert 1619 allInsertFields := `id, message_id, whisper_timestamp, chat_id, local_chat_id, clock_value, pinned, pinned_by` 1620 insertValues := strings.Repeat("?, ", strings.Count(allInsertFields, ",")) + "?" 1621 insertQuery := "INSERT INTO pin_messages(" + allInsertFields + ") VALUES (" + insertValues + ")" // nolint: gosec 1622 insertStmt, err := tx.Prepare(insertQuery) 1623 if err != nil { 1624 return nil, err 1625 } 1626 queries.insertStmt = insertStmt 1627 1628 // update 1629 updateQuery := "UPDATE pin_messages SET pinned = ?, clock_value = ?, pinned_by = ? WHERE id = ?" 1630 updateStmt, err := tx.Prepare(updateQuery) 1631 if err != nil { 1632 return nil, err 1633 } 1634 queries.updateStmt = updateStmt 1635 queries.transaction = tx 1636 return queries, nil 1637 } 1638 1639 func (db sqlitePersistence) SavePinMessages(messages []*common.PinMessage) (err error) { 1640 queries, err := db.buildPinMessageQueries() 1641 if err != nil { 1642 return 1643 } 1644 defer func() { 1645 if err == nil { 1646 err = queries.transaction.Commit() 1647 return 1648 } 1649 // don't shadow original error 1650 _ = queries.transaction.Rollback() 1651 }() 1652 for _, message := range messages { 1653 _, err = db.savePinMessage(message, queries) 1654 if err != nil { 1655 return 1656 } 1657 } 1658 1659 return 1660 } 1661 1662 func (db sqlitePersistence) savePinMessage(message *common.PinMessage, queries *insertPinMessagesQueries) (inserted bool, err error) { 1663 tx := queries.transaction 1664 selectQuery := queries.selectStmt 1665 updateStmt := queries.updateStmt 1666 insertStmt := queries.insertStmt 1667 1668 row := tx.QueryRow(selectQuery, message.ID) 1669 var existingClock uint64 1670 switch err = row.Scan(&existingClock); err { 1671 case sql.ErrNoRows: 1672 // not found, insert new record 1673 allValues := []interface{}{ 1674 message.ID, 1675 message.MessageId, 1676 message.WhisperTimestamp, 1677 message.ChatId, 1678 message.LocalChatID, 1679 message.Clock, 1680 message.Pinned, 1681 message.From, 1682 } 1683 _, err = insertStmt.Exec(allValues...) 1684 if err != nil { 1685 return 1686 } 1687 inserted = true 1688 case nil: 1689 // found, update if current message is more recent, otherwise skip 1690 if existingClock < message.Clock { 1691 // update 1692 _, err = updateStmt.Exec(message.Pinned, message.Clock, message.From, message.ID) 1693 if err != nil { 1694 return 1695 } 1696 inserted = true 1697 } 1698 1699 default: 1700 return 1701 } 1702 1703 return 1704 } 1705 1706 func (db sqlitePersistence) SavePinMessage(message *common.PinMessage) (inserted bool, err error) { 1707 queries, err := db.buildPinMessageQueries() 1708 if err != nil { 1709 return 1710 } 1711 defer func() { 1712 if err == nil { 1713 err = queries.transaction.Commit() 1714 return 1715 } 1716 // don't shadow original error 1717 _ = queries.transaction.Rollback() 1718 }() 1719 return db.savePinMessage(message, queries) 1720 } 1721 1722 func (db sqlitePersistence) DeleteMessage(id string) error { 1723 _, err := db.db.Exec(`DELETE FROM user_messages WHERE id = ?`, id) 1724 return err 1725 } 1726 1727 func (db sqlitePersistence) DeleteMessages(ids []string) error { 1728 idsArgs := make([]interface{}, 0, len(ids)) 1729 for _, id := range ids { 1730 idsArgs = append(idsArgs, id) 1731 } 1732 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 1733 1734 _, err := db.db.Exec("DELETE FROM user_messages WHERE id IN ("+inVector+")", idsArgs...) // nolint: gosec 1735 1736 return err 1737 } 1738 1739 func (db sqlitePersistence) HideMessage(id string) error { 1740 _, err := db.db.Exec(`UPDATE user_messages SET hide = 1, seen = 1 WHERE id = ?`, id) 1741 return err 1742 } 1743 1744 // SetHideOnMessage set the hide flag, but not the seen flag, as it's needed by the client to understand whether the count should be updated 1745 func (db sqlitePersistence) SetHideOnMessage(id string) error { 1746 _, err := db.db.Exec(`UPDATE user_messages SET hide = 1 WHERE id = ?`, id) 1747 return err 1748 } 1749 1750 func (db sqlitePersistence) DeleteMessagesByCommunityID(id string) error { 1751 return db.deleteMessagesByCommunityID(id) 1752 } 1753 1754 func (db sqlitePersistence) deleteMessagesByCommunityID(id string) (err error) { 1755 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1756 if err != nil { 1757 return err 1758 } 1759 defer func() { 1760 if err == nil { 1761 err = tx.Commit() 1762 return 1763 } 1764 // don't shadow original error 1765 _ = tx.Rollback() 1766 }() 1767 1768 _, err = tx.Exec(`DELETE FROM user_messages WHERE community_id = ?`, id) 1769 if err != nil { 1770 return 1771 } 1772 1773 _, err = tx.Exec(`DELETE FROM pin_messages WHERE community_id = ?`, id) 1774 1775 return 1776 } 1777 1778 func (db sqlitePersistence) DeleteMessagesByChatID(id string) error { 1779 return db.deleteMessagesByChatID(id, nil) 1780 } 1781 1782 func (db sqlitePersistence) deleteMessagesByChatID(id string, tx *sql.Tx) (err error) { 1783 if tx == nil { 1784 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1785 if err != nil { 1786 return err 1787 } 1788 defer func() { 1789 if err == nil { 1790 err = tx.Commit() 1791 return 1792 } 1793 // don't shadow original error 1794 _ = tx.Rollback() 1795 }() 1796 } 1797 1798 _, err = tx.Exec(`DELETE FROM user_messages WHERE local_chat_id = ?`, id) 1799 if err != nil { 1800 return 1801 } 1802 1803 _, err = tx.Exec(`DELETE FROM pin_messages WHERE local_chat_id = ?`, id) 1804 1805 return 1806 } 1807 1808 func (db sqlitePersistence) deleteMessagesByChatIDAndClockValueLessThanOrEqual(id string, clock uint64, tx *sql.Tx) (unViewedMessages, unViewedMentions uint, err error) { 1809 if tx == nil { 1810 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1811 if err != nil { 1812 return 0, 0, err 1813 } 1814 defer func() { 1815 if err == nil { 1816 err = tx.Commit() 1817 return 1818 } 1819 // don't shadow original error 1820 _ = tx.Rollback() 1821 }() 1822 } 1823 1824 _, err = tx.Exec(`DELETE FROM user_messages WHERE local_chat_id = ? AND clock_value <= ?`, id, clock) 1825 if err != nil { 1826 return 1827 } 1828 1829 _, err = tx.Exec(`DELETE FROM pin_messages WHERE local_chat_id = ? AND clock_value <= ?`, id, clock) 1830 if err != nil { 1831 return 1832 } 1833 1834 _, err = tx.Exec( 1835 `UPDATE chats 1836 SET unviewed_message_count = 1837 (SELECT COUNT(1) 1838 FROM user_messages 1839 WHERE local_chat_id = ? AND seen = 0), 1840 unviewed_mentions_count = 1841 (SELECT COUNT(1) 1842 FROM user_messages 1843 WHERE local_chat_id = ? AND seen = 0 AND (mentioned OR replied)), 1844 highlight = 0 1845 WHERE id = ?`, id, id, id) 1846 1847 if err != nil { 1848 return 0, 0, err 1849 } 1850 1851 err = tx.QueryRow(`SELECT unviewed_message_count, unviewed_mentions_count FROM chats 1852 WHERE id = ?`, id).Scan(&unViewedMessages, &unViewedMentions) 1853 1854 return 1855 } 1856 1857 func (db sqlitePersistence) MarkAllRead(chatID string, clock uint64) (int64, int64, error) { 1858 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1859 if err != nil { 1860 return 0, 0, err 1861 } 1862 defer func() { 1863 if err == nil { 1864 err = tx.Commit() 1865 return 1866 } 1867 // don't shadow original error 1868 _ = tx.Rollback() 1869 }() 1870 1871 seenResult, err := tx.Exec(`UPDATE user_messages SET seen = 1 WHERE local_chat_id = ? AND seen = 0 AND clock_value <= ? AND not(mentioned) AND not(replied)`, chatID, clock) 1872 if err != nil { 1873 return 0, 0, err 1874 } 1875 1876 seen, err := seenResult.RowsAffected() 1877 if err != nil { 1878 return 0, 0, err 1879 } 1880 1881 mentionedOrRepliedResult, err := tx.Exec(`UPDATE user_messages SET seen = 1 WHERE local_chat_id = ? AND seen = 0 AND clock_value <= ? AND (mentioned OR replied)`, chatID, clock) 1882 if err != nil { 1883 return 0, 0, err 1884 } 1885 1886 mentionedOrReplied, err := mentionedOrRepliedResult.RowsAffected() 1887 if err != nil { 1888 return 0, 0, err 1889 } 1890 1891 _, err = tx.Exec( 1892 `UPDATE chats 1893 SET unviewed_message_count = 0, 1894 unviewed_mentions_count = 0, 1895 highlight = 0 1896 WHERE id = ?`, chatID, chatID, chatID) 1897 1898 if err != nil { 1899 return 0, 0, err 1900 } 1901 1902 return (seen + mentionedOrReplied), mentionedOrReplied, nil 1903 } 1904 1905 func (db sqlitePersistence) MarkAllReadMultiple(chatIDs []string) error { 1906 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1907 if err != nil { 1908 return err 1909 } 1910 defer func() { 1911 if err == nil { 1912 err = tx.Commit() 1913 return 1914 } 1915 // don't shadow original error 1916 _ = tx.Rollback() 1917 }() 1918 1919 idsArgs := make([]interface{}, 0, len(chatIDs)) 1920 for _, id := range chatIDs { 1921 idsArgs = append(idsArgs, id) 1922 } 1923 1924 inVector := strings.Repeat("?, ", len(chatIDs)-1) + "?" 1925 1926 q := "UPDATE user_messages SET seen = 1 WHERE local_chat_id IN (%s) AND seen != 1" 1927 q = fmt.Sprintf(q, inVector) 1928 _, err = tx.Exec(q, idsArgs...) 1929 if err != nil { 1930 return err 1931 } 1932 1933 q = "UPDATE chats SET unviewed_mentions_count = 0, unviewed_message_count = 0, highlight = 0 WHERE id IN (%s)" 1934 q = fmt.Sprintf(q, inVector) 1935 _, err = tx.Exec(q, idsArgs...) 1936 return err 1937 } 1938 1939 func (db sqlitePersistence) MarkMessagesSeen(chatID string, ids []string) (uint64, uint64, error) { 1940 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1941 if err != nil { 1942 return 0, 0, err 1943 } 1944 defer func() { 1945 if err == nil { 1946 err = tx.Commit() 1947 return 1948 } 1949 // don't shadow original error 1950 _ = tx.Rollback() 1951 }() 1952 1953 idsArgs := make([]interface{}, 0, len(ids)) 1954 for _, id := range ids { 1955 idsArgs = append(idsArgs, id) 1956 } 1957 1958 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 1959 q := "UPDATE user_messages SET seen = 1 WHERE NOT(seen) AND (mentioned OR replied) AND id IN (" + inVector + ")" // nolint: gosec 1960 _, err = tx.Exec(q, idsArgs...) 1961 if err != nil { 1962 return 0, 0, err 1963 } 1964 1965 var countWithMentions uint64 1966 row := tx.QueryRow("SELECT changes();") 1967 if err := row.Scan(&countWithMentions); err != nil { 1968 return 0, 0, err 1969 } 1970 1971 q = "UPDATE user_messages SET seen = 1 WHERE NOT(seen) AND NOT(mentioned) AND NOT(replied) AND id IN (" + inVector + ")" // nolint: gosec 1972 _, err = tx.Exec(q, idsArgs...) 1973 if err != nil { 1974 return 0, 0, err 1975 } 1976 1977 var countNoMentions uint64 1978 row = tx.QueryRow("SELECT changes();") 1979 if err := row.Scan(&countNoMentions); err != nil { 1980 return 0, 0, err 1981 } 1982 1983 // Update denormalized count 1984 _, err = tx.Exec( 1985 `UPDATE chats 1986 SET unviewed_message_count = 1987 (SELECT COUNT(1) 1988 FROM user_messages 1989 WHERE local_chat_id = ? AND seen = 0), 1990 unviewed_mentions_count = 1991 (SELECT COUNT(1) 1992 FROM user_messages 1993 WHERE local_chat_id = ? AND seen = 0 AND (mentioned OR replied)), 1994 highlight = 0 1995 WHERE id = ?`, chatID, chatID, chatID) 1996 return countWithMentions + countNoMentions, countWithMentions, err 1997 } 1998 1999 func (db sqlitePersistence) GetMessageIdsWithGreaterTimestamp(chatID string, messageID string) ([]string, error) { 2000 var err error 2001 var rows *sql.Rows 2002 query := "SELECT id FROM user_messages WHERE local_chat_id = ? AND timestamp >= (SELECT timestamp FROM user_messages WHERE id = ?)" 2003 2004 rows, err = db.db.Query(query, chatID, messageID) 2005 2006 if err != nil { 2007 return nil, err 2008 } 2009 defer rows.Close() 2010 2011 var ids []string 2012 2013 for rows.Next() { 2014 var messageID string 2015 err = rows.Scan(&messageID) 2016 if err != nil { 2017 return nil, err 2018 } 2019 ids = append(ids, messageID) 2020 } 2021 2022 return ids, nil 2023 } 2024 2025 func (db sqlitePersistence) MarkMessageAsUnread(chatID string, messageID string) (uint64, uint64, error) { 2026 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2027 if err != nil { 2028 return 0, 0, err 2029 } 2030 defer func() { 2031 if err == nil { 2032 err = tx.Commit() 2033 return 2034 } 2035 // don't shadow original error 2036 _ = tx.Rollback() 2037 }() 2038 2039 // TODO : Reduce number of queries for getting (total unread messages, total messages with mention) 2040 // The function expected result is a pair (total unread messages, total messages with mention) 2041 // Currently a 2 step operation is needed to obtain this pair 2042 _, err = tx.Exec(`UPDATE user_messages SET seen = 1 WHERE local_chat_id = ? AND NOT(seen)`, chatID) 2043 if err != nil { 2044 return 0, 0, err 2045 } 2046 2047 _, err = tx.Exec( 2048 `UPDATE user_messages 2049 SET seen = 0 2050 WHERE local_chat_id = ? 2051 AND seen = 1 2052 AND (mentioned OR replied) 2053 AND timestamp >= (SELECT timestamp FROM user_messages WHERE id = ?)`, chatID, messageID) 2054 if err != nil { 2055 return 0, 0, err 2056 } 2057 2058 var countWithMentions uint64 2059 row := tx.QueryRow("SELECT changes();") 2060 if err := row.Scan(&countWithMentions); err != nil { 2061 return 0, 0, err 2062 } 2063 2064 _, err = tx.Exec( 2065 `UPDATE user_messages 2066 SET seen = 0 2067 WHERE local_chat_id = ? 2068 AND seen = 1 2069 AND NOT(mentioned OR replied) 2070 AND timestamp >= (SELECT timestamp FROM user_messages WHERE id = ?)`, chatID, messageID) 2071 if err != nil { 2072 return 0, 0, err 2073 } 2074 2075 var countNoMentions uint64 2076 row = tx.QueryRow("SELECT changes();") 2077 if err := row.Scan(&countNoMentions); err != nil { 2078 return 0, 0, err 2079 } 2080 2081 count := countWithMentions + countNoMentions 2082 2083 _, err = tx.Exec( 2084 `UPDATE chats 2085 SET unviewed_message_count = ?, unviewed_mentions_count = ?, 2086 highlight = 0 2087 WHERE id = ?`, count, countWithMentions, chatID) 2088 2089 return count, countWithMentions, err 2090 } 2091 2092 func (db sqlitePersistence) UpdateMessageOutgoingStatus(id string, newOutgoingStatus string) error { 2093 _, err := db.db.Exec(` 2094 UPDATE user_messages 2095 SET outgoing_status = ? 2096 WHERE id = ? AND outgoing_status != ? 2097 `, newOutgoingStatus, id, common.OutgoingStatusDelivered) 2098 return err 2099 } 2100 2101 // BlockContact updates a contact, deletes all the messages and 1-to-1 chat, updates the unread messages count and returns a map with the new count 2102 func (db sqlitePersistence) BlockContact(contact *Contact, isDesktopFunc bool) ([]*Chat, error) { 2103 var chats []*Chat 2104 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2105 if err != nil { 2106 return nil, err 2107 } 2108 defer func() { 2109 if err == nil { 2110 err = tx.Commit() 2111 return 2112 } 2113 // don't shadow original error 2114 _ = tx.Rollback() 2115 }() 2116 2117 if !isDesktopFunc { 2118 // Delete messages 2119 _, err = tx.Exec( 2120 `DELETE 2121 FROM user_messages 2122 WHERE source = ?`, 2123 contact.ID, 2124 ) 2125 if err != nil { 2126 return nil, err 2127 } 2128 } 2129 2130 // Update contact 2131 err = db.SaveContact(contact, tx) 2132 if err != nil { 2133 return nil, err 2134 } 2135 2136 if !isDesktopFunc { 2137 // Delete one-to-one chat 2138 _, err = tx.Exec("DELETE FROM chats WHERE id = ?", contact.ID) 2139 if err != nil { 2140 return nil, err 2141 } 2142 } 2143 2144 // Recalculate denormalized fields 2145 _, err = tx.Exec(` 2146 UPDATE chats 2147 SET 2148 unviewed_message_count = (SELECT COUNT(1) FROM user_messages WHERE seen = 0 AND local_chat_id = chats.id), 2149 unviewed_mentions_count = (SELECT COUNT(1) FROM user_messages WHERE seen = 0 AND local_chat_id = chats.id AND (mentioned OR replied))`) 2150 if err != nil { 2151 return nil, err 2152 } 2153 2154 // return the updated chats 2155 chats, err = db.chats(tx) 2156 if err != nil { 2157 return nil, err 2158 } 2159 for _, c := range chats { 2160 var lastMessageID string 2161 row := tx.QueryRow(`SELECT id FROM user_messages WHERE local_chat_id = ? ORDER BY clock_value DESC LIMIT 1`, c.ID) 2162 switch err := row.Scan(&lastMessageID); err { 2163 2164 case nil: 2165 message, err := db.messageByID(tx, lastMessageID) 2166 if err != nil { 2167 return nil, err 2168 } 2169 if message != nil { 2170 encodedMessage, err := json.Marshal(message) 2171 if err != nil { 2172 return nil, err 2173 } 2174 _, err = tx.Exec(`UPDATE chats SET last_message = ? WHERE id = ?`, encodedMessage, c.ID) 2175 if err != nil { 2176 return nil, err 2177 } 2178 c.LastMessage = message 2179 2180 } 2181 2182 case sql.ErrNoRows: 2183 // Reset LastMessage 2184 _, err = tx.Exec(`UPDATE chats SET last_message = NULL WHERE id = ?`, c.ID) 2185 if err != nil { 2186 return nil, err 2187 } 2188 c.LastMessage = nil 2189 default: 2190 return nil, err 2191 } 2192 } 2193 2194 return chats, err 2195 } 2196 2197 func (db sqlitePersistence) HasDiscordMessageAuthor(id string) (exists bool, err error) { 2198 err = db.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM discord_message_authors WHERE id = ?)`, id).Scan(&exists) 2199 return exists, err 2200 } 2201 2202 func (db sqlitePersistence) HasDiscordMessageAuthorImagePayload(id string) (hasPayload bool, err error) { 2203 err = db.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM discord_message_authors WHERE id = ? AND avatar_image_payload NOT NULL)`, id).Scan(&hasPayload) 2204 return hasPayload, err 2205 } 2206 2207 func (db sqlitePersistence) SaveDiscordMessageAuthor(author *protobuf.DiscordMessageAuthor) (err error) { 2208 stmt, err := db.db.Prepare(basicInsertDiscordMessageAuthorQuery) 2209 if err != nil { 2210 return 2211 } 2212 _, err = stmt.Exec( 2213 author.GetId(), 2214 author.GetName(), 2215 author.GetDiscriminator(), 2216 author.GetNickname(), 2217 author.GetAvatarUrl(), 2218 author.GetAvatarImagePayload(), 2219 ) 2220 return 2221 } 2222 2223 func (db sqlitePersistence) SaveDiscordMessageAuthors(authors []*protobuf.DiscordMessageAuthor) (err error) { 2224 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2225 if err != nil { 2226 return 2227 } 2228 defer func() { 2229 if err == nil { 2230 err = tx.Commit() 2231 return 2232 } 2233 // don't shadow original error 2234 _ = tx.Rollback() 2235 }() 2236 2237 stmt, err := tx.Prepare(basicInsertDiscordMessageAuthorQuery) 2238 if err != nil { 2239 return 2240 } 2241 defer stmt.Close() 2242 2243 for _, author := range authors { 2244 _, err = stmt.Exec( 2245 author.GetId(), 2246 author.GetName(), 2247 author.GetDiscriminator(), 2248 author.GetNickname(), 2249 author.GetAvatarUrl(), 2250 author.GetAvatarImagePayload(), 2251 ) 2252 if err != nil { 2253 return 2254 } 2255 } 2256 return 2257 } 2258 2259 func (db sqlitePersistence) UpdateDiscordMessageAuthorImage(authorID string, payload []byte) (err error) { 2260 query := "UPDATE discord_message_authors SET avatar_image_payload = ? WHERE id = ?" 2261 stmt, err := db.db.Prepare(query) 2262 if err != nil { 2263 return 2264 } 2265 defer stmt.Close() 2266 _, err = stmt.Exec(payload, authorID) 2267 return 2268 } 2269 2270 func (db sqlitePersistence) GetDiscordMessageAuthorImagePayloadByID(id string) ([]byte, error) { 2271 payload := make([]byte, 0) 2272 row := db.db.QueryRow("SELECT avatar_image_payload FROM discord_message_authors WHERE id = ?", id) 2273 err := row.Scan(&payload) 2274 return payload, err 2275 } 2276 2277 func (db sqlitePersistence) GetDiscordMessageAuthorByID(id string) (*protobuf.DiscordMessageAuthor, error) { 2278 2279 author := &protobuf.DiscordMessageAuthor{} 2280 2281 row := db.db.QueryRow("SELECT id, name, discriminator, nickname, avatar_url FROM discord_message_authors WHERE id = ?", id) 2282 err := row.Scan( 2283 &author.Id, 2284 &author.Name, 2285 &author.Discriminator, 2286 &author.Nickname, 2287 &author.AvatarUrl) 2288 return author, err 2289 } 2290 2291 func (db sqlitePersistence) SaveDiscordMessage(message *protobuf.DiscordMessage) (err error) { 2292 query := "INSERT OR REPLACE INTO discord_messages(id,type,timestamp,timestamp_edited,content,author_id, reference_message_id, reference_channel_id, reference_guild_id) VALUES (?,?,?,?,?,?,?,?,?)" 2293 stmt, err := db.db.Prepare(query) 2294 if err != nil { 2295 return 2296 } 2297 defer stmt.Close() 2298 _, err = stmt.Exec( 2299 message.GetId(), 2300 message.GetType(), 2301 message.GetTimestamp(), 2302 message.GetTimestampEdited(), 2303 message.GetContent(), 2304 message.Author.GetId(), 2305 message.Reference.GetMessageId(), 2306 message.Reference.GetChannelId(), 2307 message.Reference.GetGuildId(), 2308 ) 2309 return 2310 } 2311 2312 func (db sqlitePersistence) SaveDiscordMessages(messages []*protobuf.DiscordMessage) (err error) { 2313 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2314 if err != nil { 2315 return 2316 } 2317 defer func() { 2318 if err == nil { 2319 err = tx.Commit() 2320 return 2321 } 2322 // don't shadow original error 2323 _ = tx.Rollback() 2324 }() 2325 2326 query := "INSERT OR REPLACE INTO discord_messages(id, author_id, type, timestamp, timestamp_edited, content, reference_message_id, reference_channel_id, reference_guild_id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)" 2327 stmt, err := tx.Prepare(query) 2328 if err != nil { 2329 return 2330 } 2331 defer stmt.Close() 2332 2333 for _, msg := range messages { 2334 _, err = stmt.Exec( 2335 msg.GetId(), 2336 msg.Author.GetId(), 2337 msg.GetType(), 2338 msg.GetTimestamp(), 2339 msg.GetTimestampEdited(), 2340 msg.GetContent(), 2341 msg.Reference.GetMessageId(), 2342 msg.Reference.GetChannelId(), 2343 msg.Reference.GetGuildId(), 2344 ) 2345 if err != nil { 2346 return 2347 } 2348 } 2349 return 2350 } 2351 2352 func (db sqlitePersistence) HasDiscordMessageAttachmentPayload(id string, messageID string) (hasPayload bool, err error) { 2353 err = db.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM discord_message_attachments WHERE id = ? AND discord_message_id = ? AND payload NOT NULL)`, id, messageID).Scan(&hasPayload) 2354 return hasPayload, err 2355 } 2356 2357 func (db sqlitePersistence) SaveDiscordMessageAttachments(attachments []*protobuf.DiscordMessageAttachment) (err error) { 2358 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2359 if err != nil { 2360 return 2361 } 2362 defer func() { 2363 if err == nil { 2364 err = tx.Commit() 2365 return 2366 } 2367 // don't shadow original error 2368 _ = tx.Rollback() 2369 }() 2370 2371 stmt, err := tx.Prepare("INSERT OR REPLACE INTO discord_message_attachments(id,discord_message_id,url,file_name,file_size_bytes,payload, content_type) VALUES (?,?,?,?,?,?,?)") 2372 if err != nil { 2373 return 2374 } 2375 defer stmt.Close() 2376 if err != nil { 2377 return 2378 } 2379 2380 for _, attachment := range attachments { 2381 _, err = stmt.Exec( 2382 attachment.GetId(), 2383 attachment.GetMessageId(), 2384 attachment.GetUrl(), 2385 attachment.GetFileName(), 2386 attachment.GetFileSizeBytes(), 2387 attachment.GetPayload(), 2388 attachment.GetContentType(), 2389 ) 2390 if err != nil { 2391 return 2392 } 2393 } 2394 return 2395 } 2396 2397 func (db sqlitePersistence) SaveEmojiReaction(emojiReaction *EmojiReaction) (err error) { 2398 query := "INSERT INTO emoji_reactions(id,clock_value,source,emoji_id,message_id,chat_id,local_chat_id,retracted) VALUES (?,?,?,?,?,?,?,?)" 2399 stmt, err := db.db.Prepare(query) 2400 if err != nil { 2401 return 2402 } 2403 2404 _, err = stmt.Exec( 2405 emojiReaction.ID(), 2406 emojiReaction.Clock, 2407 emojiReaction.From, 2408 emojiReaction.Type, 2409 emojiReaction.MessageId, 2410 emojiReaction.ChatId, 2411 emojiReaction.LocalChatID, 2412 emojiReaction.Retracted, 2413 ) 2414 2415 return 2416 } 2417 2418 func (db sqlitePersistence) EmojiReactionByID(id string) (*EmojiReaction, error) { 2419 row := db.db.QueryRow( 2420 `SELECT 2421 clock_value, 2422 source, 2423 emoji_id, 2424 message_id, 2425 chat_id, 2426 local_chat_id, 2427 retracted 2428 FROM 2429 emoji_reactions 2430 WHERE 2431 emoji_reactions.id = ? 2432 `, id) 2433 2434 emojiReaction := NewEmojiReaction() 2435 err := row.Scan(&emojiReaction.Clock, 2436 &emojiReaction.From, 2437 &emojiReaction.Type, 2438 &emojiReaction.MessageId, 2439 &emojiReaction.ChatId, 2440 &emojiReaction.LocalChatID, 2441 &emojiReaction.Retracted, 2442 ) 2443 2444 switch err { 2445 case sql.ErrNoRows: 2446 return nil, common.ErrRecordNotFound 2447 case nil: 2448 return emojiReaction, nil 2449 default: 2450 return nil, err 2451 } 2452 } 2453 2454 func (db sqlitePersistence) SaveInvitation(invitation *GroupChatInvitation) (err error) { 2455 query := "INSERT INTO group_chat_invitations(id,source,chat_id,message,state,clock) VALUES (?,?,?,?,?,?)" 2456 stmt, err := db.db.Prepare(query) 2457 if err != nil { 2458 return 2459 } 2460 _, err = stmt.Exec( 2461 invitation.ID(), 2462 invitation.From, 2463 invitation.ChatId, 2464 invitation.IntroductionMessage, 2465 invitation.State, 2466 invitation.Clock, 2467 ) 2468 2469 return 2470 } 2471 2472 func (db sqlitePersistence) GetGroupChatInvitations() (rst []*GroupChatInvitation, err error) { 2473 2474 tx, err := db.db.Begin() 2475 if err != nil { 2476 return 2477 } 2478 defer func() { 2479 if err == nil { 2480 err = tx.Commit() 2481 return 2482 } 2483 _ = tx.Rollback() 2484 }() 2485 2486 bRows, err := tx.Query(`SELECT 2487 source, 2488 chat_id, 2489 message, 2490 state, 2491 clock 2492 FROM 2493 group_chat_invitations`) 2494 if err != nil { 2495 return 2496 } 2497 defer bRows.Close() 2498 for bRows.Next() { 2499 invitation := GroupChatInvitation{} 2500 err = bRows.Scan( 2501 &invitation.From, 2502 &invitation.ChatId, 2503 &invitation.IntroductionMessage, 2504 &invitation.State, 2505 &invitation.Clock) 2506 if err != nil { 2507 return nil, err 2508 } 2509 rst = append(rst, &invitation) 2510 } 2511 2512 return rst, nil 2513 } 2514 2515 func (db sqlitePersistence) InvitationByID(id string) (*GroupChatInvitation, error) { 2516 row := db.db.QueryRow( 2517 `SELECT 2518 source, 2519 chat_id, 2520 message, 2521 state, 2522 clock 2523 FROM 2524 group_chat_invitations 2525 WHERE 2526 group_chat_invitations.id = ? 2527 `, id) 2528 2529 chatInvitations := NewGroupChatInvitation() 2530 err := row.Scan(&chatInvitations.From, 2531 &chatInvitations.ChatId, 2532 &chatInvitations.IntroductionMessage, 2533 &chatInvitations.State, 2534 &chatInvitations.Clock, 2535 ) 2536 2537 switch err { 2538 case sql.ErrNoRows: 2539 return nil, common.ErrRecordNotFound 2540 case nil: 2541 return chatInvitations, nil 2542 default: 2543 return nil, err 2544 } 2545 } 2546 2547 // ClearHistory deletes all the messages for a chat and updates it's values 2548 func (db sqlitePersistence) ClearHistory(chat *Chat, currentClockValue uint64) (err error) { 2549 var tx *sql.Tx 2550 2551 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2552 if err != nil { 2553 return 2554 } 2555 2556 defer func() { 2557 if err == nil { 2558 err = tx.Commit() 2559 return 2560 } 2561 // don't shadow original error 2562 _ = tx.Rollback() 2563 }() 2564 err = db.clearHistory(chat, currentClockValue, tx, false) 2565 2566 return 2567 } 2568 2569 func (db sqlitePersistence) ClearHistoryFromSyncMessage(chat *Chat, currentClockValue uint64) (err error) { 2570 var tx *sql.Tx 2571 2572 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2573 if err != nil { 2574 return 2575 } 2576 2577 defer func() { 2578 if err == nil { 2579 err = tx.Commit() 2580 return 2581 } 2582 // don't shadow original error 2583 _ = tx.Rollback() 2584 }() 2585 err = db.clearHistoryFromSyncMessage(chat, currentClockValue, tx) 2586 2587 return 2588 } 2589 2590 // Deactivate chat sets a chat as inactive and clear its history 2591 func (db sqlitePersistence) DeactivateChat(chat *Chat, currentClockValue uint64, doClearHistory bool) (err error) { 2592 var tx *sql.Tx 2593 2594 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 2595 if err != nil { 2596 return 2597 } 2598 2599 defer func() { 2600 if err == nil { 2601 err = tx.Commit() 2602 return 2603 } 2604 // don't shadow original error 2605 _ = tx.Rollback() 2606 }() 2607 err = db.deactivateChat(chat, currentClockValue, tx, doClearHistory) 2608 2609 return 2610 } 2611 2612 func (db sqlitePersistence) deactivateChat(chat *Chat, currentClockValue uint64, tx *sql.Tx, doClearHistory bool) error { 2613 chat.Active = false 2614 err := db.saveChat(tx, *chat) 2615 if err != nil { 2616 return err 2617 } 2618 2619 if !doClearHistory { 2620 return nil 2621 } 2622 return db.clearHistory(chat, currentClockValue, tx, true) 2623 } 2624 2625 func (db sqlitePersistence) SaveDelete(deleteMessage *DeleteMessage) error { 2626 _, err := db.db.Exec(`INSERT INTO user_messages_deletes (clock, chat_id, message_id, source, id) VALUES(?,?,?,?,?)`, deleteMessage.Clock, deleteMessage.ChatId, deleteMessage.MessageId, deleteMessage.From, deleteMessage.ID) 2627 return err 2628 } 2629 2630 func (db sqlitePersistence) GetDeletes(messageID string, from string) ([]*DeleteMessage, error) { 2631 rows, err := db.db.Query(`SELECT clock, chat_id, message_id, source, id FROM user_messages_deletes WHERE message_id = ? AND source = ? ORDER BY CLOCK DESC`, messageID, from) 2632 if err != nil { 2633 return nil, err 2634 } 2635 defer rows.Close() 2636 2637 var messages []*DeleteMessage 2638 for rows.Next() { 2639 d := NewDeleteMessage() 2640 err := rows.Scan(&d.Clock, &d.ChatId, &d.MessageId, &d.From, &d.ID) 2641 if err != nil { 2642 return nil, err 2643 } 2644 messages = append(messages, d) 2645 } 2646 return messages, nil 2647 } 2648 2649 func (db sqlitePersistence) SaveOrUpdateDeleteForMeMessage(deleteForMeMessage *protobuf.SyncDeleteForMeMessage) error { 2650 _, err := db.db.Exec(`INSERT OR REPLACE INTO user_messages_deleted_for_mes (clock, message_id) 2651 SELECT ?,? WHERE NOT EXISTS (SELECT 1 FROM user_messages_deleted_for_mes WHERE message_id = ? AND clock >= ?)`, 2652 deleteForMeMessage.Clock, deleteForMeMessage.MessageId, deleteForMeMessage.MessageId, deleteForMeMessage.Clock) 2653 return err 2654 } 2655 2656 func (db sqlitePersistence) GetDeleteForMeMessagesByMessageID(messageID string) ([]*protobuf.SyncDeleteForMeMessage, error) { 2657 rows, err := db.db.Query(`SELECT clock, message_id FROM user_messages_deleted_for_mes WHERE message_id = ?`, messageID) 2658 if err != nil { 2659 return nil, err 2660 } 2661 defer rows.Close() 2662 2663 var messages []*protobuf.SyncDeleteForMeMessage 2664 for rows.Next() { 2665 d := &protobuf.SyncDeleteForMeMessage{} 2666 err := rows.Scan(&d.Clock, &d.MessageId) 2667 if err != nil { 2668 return nil, err 2669 } 2670 messages = append(messages, d) 2671 } 2672 return messages, nil 2673 } 2674 2675 func (db sqlitePersistence) GetDeleteForMeMessages() ([]*protobuf.SyncDeleteForMeMessage, error) { 2676 rows, err := db.db.Query(`SELECT clock, message_id FROM user_messages_deleted_for_mes`) 2677 if err != nil { 2678 return nil, err 2679 } 2680 defer rows.Close() 2681 2682 var messages []*protobuf.SyncDeleteForMeMessage 2683 for rows.Next() { 2684 d := &protobuf.SyncDeleteForMeMessage{} 2685 err := rows.Scan(&d.Clock, &d.MessageId) 2686 if err != nil { 2687 return nil, err 2688 } 2689 messages = append(messages, d) 2690 } 2691 return messages, nil 2692 } 2693 2694 func (db sqlitePersistence) SaveEdit(editMessage *EditMessage) error { 2695 if editMessage == nil { 2696 return nil 2697 } 2698 2699 _, err := db.db.Exec(`INSERT INTO user_messages_edits (clock, chat_id, message_id, text, source, id, unfurled_links, unfurled_status_links) VALUES(?,?,?,?,?,?,?,?)`, editMessage.Clock, editMessage.ChatId, editMessage.MessageId, editMessage.Text, editMessage.From, editMessage.ID, pq.Array(editMessage.UnfurledLinks), editMessage.UnfurledStatusLinks) 2700 return err 2701 } 2702 2703 func (db sqlitePersistence) GetEdits(messageID string, from string) ([]*EditMessage, error) { 2704 rows, err := db.db.Query(`SELECT clock, chat_id, message_id, source, text, id, unfurled_links, unfurled_status_links FROM user_messages_edits WHERE message_id = ? AND source = ? ORDER BY CLOCK DESC`, messageID, from) 2705 if err != nil { 2706 return nil, err 2707 } 2708 defer rows.Close() 2709 2710 var messages []*EditMessage 2711 for rows.Next() { 2712 e := NewEditMessage() 2713 err := rows.Scan(&e.Clock, &e.ChatId, &e.MessageId, &e.From, &e.Text, &e.ID, pq.Array(&e.UnfurledLinks), &e.UnfurledStatusLinks) 2714 if err != nil { 2715 return nil, err 2716 } 2717 messages = append(messages, e) 2718 2719 } 2720 return messages, nil 2721 } 2722 2723 func (db sqlitePersistence) clearHistory(chat *Chat, currentClockValue uint64, tx *sql.Tx, deactivate bool) error { 2724 // Set deleted at clock value if it's not a public chat so that 2725 // old messages will be discarded, or if it's a straight clear history 2726 if !deactivate || (!chat.Public() && !chat.ProfileUpdates() && !chat.Timeline()) { 2727 if chat.LastMessage != nil && chat.LastMessage.Clock != 0 { 2728 chat.DeletedAtClockValue = chat.LastMessage.Clock 2729 } 2730 chat.DeletedAtClockValue = currentClockValue 2731 } 2732 2733 // Reset synced-to/from 2734 syncedTo := uint32(currentClockValue / 1000) 2735 chat.SyncedTo = syncedTo 2736 chat.SyncedFrom = 0 2737 2738 chat.LastMessage = nil 2739 chat.UnviewedMessagesCount = 0 2740 chat.UnviewedMentionsCount = 0 2741 chat.Highlight = true 2742 2743 err := db.deleteMessagesByChatID(chat.ID, tx) 2744 if err != nil { 2745 return err 2746 } 2747 2748 err = db.saveChat(tx, *chat) 2749 return err 2750 } 2751 2752 func (db sqlitePersistence) clearHistoryFromSyncMessage(chat *Chat, clearedAt uint64, tx *sql.Tx) error { 2753 chat.DeletedAtClockValue = clearedAt 2754 2755 // Reset synced-to/from 2756 syncedTo := uint32(clearedAt / 1000) 2757 chat.SyncedTo = syncedTo 2758 chat.SyncedFrom = 0 2759 2760 unViewedMessagesCount, unViewedMentionsCount, err := db.deleteMessagesByChatIDAndClockValueLessThanOrEqual(chat.ID, clearedAt, tx) 2761 if err != nil { 2762 return err 2763 } 2764 2765 chat.UnviewedMessagesCount = unViewedMessagesCount 2766 chat.UnviewedMentionsCount = unViewedMentionsCount 2767 2768 if chat.LastMessage != nil && chat.LastMessage.Clock <= clearedAt { 2769 chat.LastMessage = nil 2770 } 2771 2772 err = db.saveChat(tx, *chat) 2773 return err 2774 } 2775 2776 func (db sqlitePersistence) SetContactRequestState(id string, state common.ContactRequestState) error { 2777 _, err := db.db.Exec(`UPDATE user_messages SET contact_request_state = ? WHERE id = ?`, state, id) 2778 return err 2779 } 2780 2781 func getUpdatedChatMessagePayload(originalMessage *protobuf.DiscordMessage, attachmentMessage *protobuf.DiscordMessage) *protobuf.ChatMessage_DiscordMessage { 2782 originalMessage.Attachments = append(originalMessage.Attachments, attachmentMessage.Attachments...) 2783 return &protobuf.ChatMessage_DiscordMessage{ 2784 DiscordMessage: originalMessage, 2785 } 2786 } 2787 2788 func getMessageFromScanRows(db sqlitePersistence, rows *sql.Rows) (*common.Message, error) { 2789 var msg *common.Message 2790 2791 for rows.Next() { 2792 // There's a possibility of multiple rows per message if the 2793 // message has a discordMessage and the discordMessage has multiple 2794 // attachments 2795 // 2796 // Hence, we make sure we're aggregating all attachments on a single 2797 // common.Message 2798 message := common.NewMessage() 2799 err := db.tableUserMessagesScanAllFields(rows, message) 2800 if err != nil { 2801 return nil, err 2802 } 2803 2804 if msg == nil { 2805 msg = message 2806 } else if discordMessage := msg.GetDiscordMessage(); discordMessage != nil { 2807 msg.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage()) 2808 } 2809 } 2810 if msg == nil { 2811 return nil, common.ErrRecordNotFound 2812 } 2813 return msg, nil 2814 } 2815 2816 type HasClocks interface { 2817 GetClock(i int) uint64 2818 } 2819 2820 func SortByClock(msgs HasClocks) { 2821 sort.Slice(msgs, func(i, j int) bool { 2822 return msgs.GetClock(j) < msgs.GetClock(i) 2823 }) 2824 } 2825 2826 func getMessagesFromScanRows(db sqlitePersistence, rows *sql.Rows, withCursor bool) ([]*common.Message, error) { 2827 messageIdx := make(map[string]*common.Message, 0) 2828 var messages common.Messages 2829 for rows.Next() { 2830 // There's a possibility of multiple rows per message if the 2831 // message has a discordMessage and the discordMessage has multiple 2832 // attachments 2833 // 2834 // Hence, we make sure we're aggregating all attachments on a single 2835 // common.Message 2836 message := common.NewMessage() 2837 2838 if withCursor { 2839 var cursor string 2840 if err := db.tableUserMessagesScanAllFields(rows, message, &cursor); err != nil { 2841 return nil, err 2842 } 2843 } else { 2844 if err := db.tableUserMessagesScanAllFields(rows, message); err != nil { 2845 return nil, err 2846 } 2847 } 2848 2849 if msg, ok := messageIdx[message.ID]; !ok { 2850 messageIdx[message.ID] = message 2851 messages = append(messages, message) 2852 } else if discordMessage := msg.GetDiscordMessage(); discordMessage != nil { 2853 msg.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage()) 2854 } 2855 } 2856 2857 SortByClock(messages) 2858 2859 return messages, nil 2860 } 2861 2862 func getMessagesAndCursorsFromScanRows(db sqlitePersistence, rows *sql.Rows) ([]*common.Message, []string, error) { 2863 2864 var cursors []string 2865 var messages common.Messages 2866 messageIdx := make(map[string]*common.Message, 0) 2867 for rows.Next() { 2868 // There's a possibility of multiple rows per message if the 2869 // message has a discordMessage and the discordMessage has multiple 2870 // attachments 2871 // 2872 // Hence, we make sure we're aggregating all attachments on a single 2873 // common.Message 2874 2875 var cursor string 2876 message := common.NewMessage() 2877 if err := db.tableUserMessagesScanAllFields(rows, message, &cursor); err != nil { 2878 return nil, nil, err 2879 } 2880 2881 if msg, ok := messageIdx[message.ID]; !ok { 2882 messageIdx[message.ID] = message 2883 cursors = append(cursors, cursor) 2884 messages = append(messages, message) 2885 } else if discordMessage := msg.GetDiscordMessage(); discordMessage != nil { 2886 msg.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage()) 2887 } 2888 } 2889 2890 SortByClock(messages) 2891 2892 return messages, cursors, nil 2893 } 2894 2895 func getPinnedMessagesAndCursorsFromScanRows(db sqlitePersistence, rows *sql.Rows) ([]*common.PinnedMessage, []string, error) { 2896 2897 var cursors []string 2898 var messages common.PinnedMessages 2899 messageIdx := make(map[string]*common.PinnedMessage, 0) 2900 2901 for rows.Next() { 2902 var ( 2903 pinnedAt uint64 2904 pinnedBy string 2905 cursor string 2906 ) 2907 message := common.NewMessage() 2908 if err := db.tableUserMessagesScanAllFields(rows, message, &pinnedAt, &pinnedBy, &cursor); err != nil { 2909 return nil, nil, err 2910 } 2911 if msg, ok := messageIdx[message.ID]; !ok { 2912 pinnedMessage := &common.PinnedMessage{ 2913 Message: message, 2914 PinnedAt: pinnedAt, 2915 PinnedBy: pinnedBy, 2916 } 2917 messageIdx[message.ID] = pinnedMessage 2918 messages = append(messages, pinnedMessage) 2919 cursors = append(cursors, cursor) 2920 } else if discordMessage := msg.Message.GetDiscordMessage(); discordMessage != nil { 2921 msg.Message.Payload = getUpdatedChatMessagePayload(discordMessage, message.GetDiscordMessage()) 2922 } 2923 } 2924 2925 SortByClock(messages) 2926 2927 return messages, cursors, nil 2928 } 2929 2930 func (db sqlitePersistence) saveBridgeMessage(tx *sql.Tx, message *protobuf.BridgeMessage, userMessageID string) (err error) { 2931 query := "INSERT INTO bridge_messages(user_messages_id,bridge_name,user_name,user_avatar,user_id,content,message_id,parent_message_id) VALUES (?,?,?,?,?,?,?,?)" 2932 stmt, err := tx.Prepare(query) 2933 if err != nil { 2934 return 2935 } 2936 defer stmt.Close() 2937 _, err = stmt.Exec( 2938 userMessageID, 2939 message.GetBridgeName(), 2940 message.GetUserName(), 2941 message.GetUserAvatar(), 2942 message.GetUserID(), 2943 message.GetContent(), 2944 message.GetMessageID(), 2945 message.GetParentMessageID(), 2946 ) 2947 return 2948 } 2949 2950 func (db sqlitePersistence) GetCommunityMemberMessagesToDelete(member string, communityID string) ([]*protobuf.DeleteCommunityMemberMessage, error) { 2951 rows, err := db.db.Query(`SELECT m.id, m.chat_id FROM user_messages as m 2952 INNER JOIN chats AS ch ON ch.id = m.chat_id AND ch.community_id = ? 2953 WHERE m.source = ?`, communityID, member) 2954 2955 if err != nil { 2956 return nil, err 2957 } 2958 2959 defer rows.Close() 2960 2961 result := []*protobuf.DeleteCommunityMemberMessage{} 2962 2963 for rows.Next() { 2964 removeMsgsInfo := &protobuf.DeleteCommunityMemberMessage{} 2965 err = rows.Scan(&removeMsgsInfo.Id, &removeMsgsInfo.ChatId) 2966 if err != nil { 2967 return nil, err 2968 } 2969 result = append(result, removeMsgsInfo) 2970 } 2971 2972 return result, nil 2973 } 2974 2975 // Finds status messages id which are replies for bridgeMessageID 2976 func (db sqlitePersistence) findStatusMessageIdsReplies(tx *sql.Tx, bridgeMessageID string) ([]string, error) { 2977 rows, err := tx.Query(`SELECT user_messages_id FROM bridge_messages WHERE parent_message_id = ?`, bridgeMessageID) 2978 if err != nil { 2979 return []string{}, err 2980 } 2981 defer rows.Close() 2982 2983 var statusMessageIDs []string 2984 for rows.Next() { 2985 var statusMessageID string 2986 err = rows.Scan(&statusMessageID) 2987 if err != nil { 2988 return []string{}, err 2989 } 2990 statusMessageIDs = append(statusMessageIDs, statusMessageID) 2991 } 2992 return statusMessageIDs, nil 2993 } 2994 2995 func (db sqlitePersistence) FindStatusMessageIDForBridgeMessageID(messageID string) (string, error) { 2996 rows, err := db.db.Query(`SELECT user_messages_id FROM bridge_messages WHERE message_id = ?`, messageID) 2997 if err != nil { 2998 return "", err 2999 } 3000 defer rows.Close() 3001 3002 if rows.Next() { 3003 var statusMessageID string 3004 err = rows.Scan(&statusMessageID) 3005 if err != nil { 3006 return "", err 3007 } 3008 return statusMessageID, nil 3009 } 3010 return "", nil 3011 } 3012 3013 func (db sqlitePersistence) findStatusMessageIDForBridgeMessageID(tx *sql.Tx, messageID string) (string, error) { 3014 rows, err := tx.Query(`SELECT user_messages_id FROM bridge_messages WHERE message_id = ?`, messageID) 3015 if err != nil { 3016 return "", err 3017 } 3018 defer rows.Close() 3019 3020 if rows.Next() { 3021 var statusMessageID string 3022 err = rows.Scan(&statusMessageID) 3023 if err != nil { 3024 return "", err 3025 } 3026 return statusMessageID, nil 3027 } 3028 return "", nil 3029 } 3030 3031 func (db sqlitePersistence) updateStatusMessagesWithResponse(tx *sql.Tx, statusMessagesToUpdate []string, responseValue string) error { 3032 sql := "UPDATE user_messages SET response_to = ? WHERE id IN (?" + strings.Repeat(",?", len(statusMessagesToUpdate)-1) + ")" //nolint: gosec 3033 stmt, err := tx.Prepare(sql) 3034 if err != nil { 3035 return err 3036 } 3037 defer stmt.Close() 3038 3039 args := make([]interface{}, 0, len(statusMessagesToUpdate)+1) 3040 args = append(args, responseValue) 3041 for _, msgToUpdate := range statusMessagesToUpdate { 3042 args = append(args, msgToUpdate) 3043 } 3044 _, err = stmt.Exec(args...) 3045 return err 3046 } 3047 3048 func (db sqlitePersistence) bridgeMessageExists(tx *sql.Tx, bridgeMessageID string) (exists bool, err error) { 3049 err = tx.QueryRow(`SELECT EXISTS(SELECT 1 FROM bridge_messages WHERE message_id = ?)`, bridgeMessageID).Scan(&exists) 3050 return exists, err 3051 } 3052 3053 func (db sqlitePersistence) updateBridgeMessageContent(tx *sql.Tx, bridgeMessageID string, content string) error { 3054 sql := "UPDATE bridge_messages SET content = ? WHERE message_id = ?" 3055 stmt, err := tx.Prepare(sql) 3056 if err != nil { 3057 return err 3058 } 3059 defer stmt.Close() 3060 3061 _, err = stmt.Exec(content, bridgeMessageID) 3062 return err 3063 } 3064 3065 // Finds if there are any messages that are replies to that message (in case replies were received earlier) 3066 func (db sqlitePersistence) findAndUpdateReplies(tx *sql.Tx, bridgeMessageID string, statusMessageID string) error { 3067 replyMessageIds, err := db.findStatusMessageIdsReplies(tx, bridgeMessageID) 3068 if err != nil { 3069 return err 3070 } 3071 if len(replyMessageIds) == 0 { 3072 return nil 3073 } 3074 return db.updateStatusMessagesWithResponse(tx, replyMessageIds, statusMessageID) 3075 } 3076 3077 func (db sqlitePersistence) findAndUpdateRepliedTo(tx *sql.Tx, discordParentMessageID string, statusMessageID string) error { 3078 // Finds status messages id which are replies for bridgeMessageID 3079 repliedMessageID, err := db.findStatusMessageIDForBridgeMessageID(tx, discordParentMessageID) 3080 if err != nil { 3081 return err 3082 } 3083 if repliedMessageID == "" { 3084 return nil 3085 } 3086 return db.updateStatusMessagesWithResponse(tx, []string{statusMessageID}, repliedMessageID) 3087 } 3088 3089 func (db sqlitePersistence) GetCommunityMemberAllMessages(member string, communityID string) ([]*common.Message, error) { 3090 additionalRequestData := "INNER JOIN chats AS ch ON ch.id = m1.chat_id AND ch.community_id = ? WHERE m1.source = ?" 3091 query := db.buildMessagesQueryWithAdditionalFields("", additionalRequestData) 3092 3093 rows, err := db.db.Query(query, communityID, member) 3094 3095 if err != nil { 3096 if err == sql.ErrNoRows { 3097 return []*common.Message{}, nil 3098 } 3099 3100 return nil, err 3101 } 3102 3103 return getMessagesFromScanRows(db, rows, false) 3104 }