github.com/status-im/status-go@v1.1.0/protocol/activity_center_persistence.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "fmt" 8 "strings" 9 10 "github.com/status-im/status-go/eth-node/types" 11 "github.com/status-im/status-go/protocol/common" 12 ) 13 14 const allFieldsForTableActivityCenterNotification = `id, timestamp, notification_type, chat_id, read, dismissed, accepted, message, author, 15 reply_message, community_id, membership_status, contact_verification_status, token_data, deleted, updated_at` 16 17 var emptyNotifications = make([]*ActivityCenterNotification, 0) 18 19 func (db sqlitePersistence) DeleteActivityCenterNotificationByID(id []byte, updatedAt uint64) error { 20 _, err := db.db.Exec(`UPDATE activity_center_notifications SET deleted = 1, updated_at = ? WHERE id = ? AND NOT deleted`, updatedAt, id) 21 return err 22 } 23 24 func (db sqlitePersistence) DeleteActivityCenterNotificationForMessage(chatID string, messageID string, updatedAt uint64) ([]*ActivityCenterNotification, error) { 25 var tx *sql.Tx 26 var err error 27 28 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 29 if err != nil { 30 return nil, err 31 } 32 defer func() { 33 if err == nil { 34 err = tx.Commit() 35 return 36 } 37 // don't shadow original error 38 _ = tx.Rollback() 39 }() 40 41 params := activityCenterQueryParams{ 42 chatID: chatID, 43 } 44 45 _, notifications, err := db.buildActivityCenterQuery(tx, params) 46 47 if err != nil { 48 return nil, err 49 } 50 51 var ids []types.HexBytes 52 var matchNotifications []*ActivityCenterNotification 53 withNotification := func(a *ActivityCenterNotification) { 54 a.Read = true 55 a.Dismissed = true 56 a.Deleted = true 57 a.UpdatedAt = updatedAt 58 ids = append(ids, a.ID) 59 matchNotifications = append(matchNotifications, a) 60 } 61 62 for _, notification := range notifications { 63 if notification.Message != nil && notification.Message.ID == messageID { 64 withNotification(notification) 65 } 66 } 67 68 if len(ids) > 0 { 69 args := make([]interface{}, 0, len(ids)+1) 70 args = append(args, updatedAt) 71 for _, id := range ids { 72 args = append(args, id) 73 } 74 75 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 76 query := "UPDATE activity_center_notifications SET read = 1, dismissed = 1, deleted = 1, updated_at = ? WHERE id IN (" + inVector + ")" // nolint: gosec 77 _, err = tx.Exec(query, args...) 78 return matchNotifications, err 79 } 80 81 return matchNotifications, nil 82 } 83 84 func (db sqlitePersistence) SaveActivityCenterNotification(notification *ActivityCenterNotification, updateState bool) (int64, error) { 85 var tx *sql.Tx 86 var err error 87 88 err = notification.Valid() 89 if err != nil { 90 return 0, err 91 } 92 93 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 94 if err != nil { 95 return 0, err 96 } 97 defer func() { 98 if err == nil { 99 err = tx.Commit() 100 return 101 } 102 // don't shadow original error 103 _ = tx.Rollback() 104 }() 105 106 // encode message 107 var encodedMessage []byte 108 if notification.Message != nil { 109 encodedMessage, err = json.Marshal(notification.Message) 110 if err != nil { 111 return 0, err 112 } 113 } 114 115 // encode message 116 var encodedReplyMessage []byte 117 if notification.ReplyMessage != nil { 118 encodedReplyMessage, err = json.Marshal(notification.ReplyMessage) 119 if err != nil { 120 return 0, err 121 } 122 } 123 124 // encode token data 125 var encodedTokenData []byte 126 if notification.TokenData != nil { 127 encodedTokenData, err = json.Marshal(notification.TokenData) 128 if err != nil { 129 return 0, err 130 } 131 } 132 133 result, err := tx.Exec(` 134 INSERT OR REPLACE 135 INTO activity_center_notifications ( 136 id, 137 timestamp, 138 notification_type, 139 chat_id, 140 community_id, 141 membership_status, 142 message, 143 reply_message, 144 author, 145 contact_verification_status, 146 read, 147 accepted, 148 dismissed, 149 token_data, 150 deleted, 151 updated_at 152 ) 153 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) 154 `, 155 notification.ID, 156 notification.Timestamp, 157 notification.Type, 158 notification.ChatID, 159 notification.CommunityID, 160 notification.MembershipStatus, 161 encodedMessage, 162 encodedReplyMessage, 163 notification.Author, 164 notification.ContactVerificationStatus, 165 notification.Read, 166 notification.Accepted, 167 notification.Dismissed, 168 encodedTokenData, 169 notification.Deleted, 170 notification.UpdatedAt, 171 ) 172 if err != nil { 173 return 0, err 174 } 175 n, err := result.RowsAffected() 176 if err != nil { 177 return n, err 178 } 179 180 // When we have inserted or updated unread notification - mark whole activity_center_settings as unseen 181 if updateState && n > 0 && !notification.Read { 182 _, err = tx.Exec(`UPDATE activity_center_states SET has_seen = 0, updated_at = ?`, notification.UpdatedAt) 183 } 184 185 return n, nil 186 } 187 188 func (db sqlitePersistence) parseRowFromTableActivityCenterNotification(rows *sql.Rows, withNotification func(notification *ActivityCenterNotification)) ([]*ActivityCenterNotification, error) { 189 var notifications []*ActivityCenterNotification 190 defer rows.Close() 191 for rows.Next() { 192 var chatID sql.NullString 193 var communityID sql.NullString 194 var messageBytes []byte 195 var replyMessageBytes []byte 196 var tokenDataBytes []byte 197 var author sql.NullString 198 notification := &ActivityCenterNotification{} 199 err := rows.Scan( 200 ¬ification.ID, 201 ¬ification.Timestamp, 202 ¬ification.Type, 203 &chatID, 204 ¬ification.Read, 205 ¬ification.Dismissed, 206 ¬ification.Accepted, 207 &messageBytes, 208 &author, 209 &replyMessageBytes, 210 &communityID, 211 ¬ification.MembershipStatus, 212 ¬ification.ContactVerificationStatus, 213 &tokenDataBytes, 214 ¬ification.Deleted, 215 ¬ification.UpdatedAt, 216 ) 217 if err != nil { 218 return nil, err 219 } 220 221 if chatID.Valid { 222 notification.ChatID = chatID.String 223 } 224 225 if communityID.Valid { 226 notification.CommunityID = communityID.String 227 } 228 229 if author.Valid { 230 notification.Author = author.String 231 } 232 233 if len(tokenDataBytes) > 0 { 234 err = json.Unmarshal(tokenDataBytes, ¬ification.TokenData) 235 if err != nil { 236 return nil, err 237 } 238 } 239 240 if len(messageBytes) > 0 { 241 err = json.Unmarshal(messageBytes, ¬ification.Message) 242 if err != nil { 243 return nil, err 244 } 245 } 246 247 if len(replyMessageBytes) > 0 { 248 err = json.Unmarshal(replyMessageBytes, ¬ification.ReplyMessage) 249 if err != nil { 250 return nil, err 251 } 252 } 253 254 if withNotification != nil { 255 withNotification(notification) 256 } 257 notifications = append(notifications, notification) 258 } 259 260 return notifications, nil 261 } 262 263 func (db sqlitePersistence) unmarshalActivityCenterNotificationRow(row *sql.Row) (*ActivityCenterNotification, error) { 264 var chatID sql.NullString 265 var communityID sql.NullString 266 var lastMessageBytes []byte 267 var messageBytes []byte 268 var replyMessageBytes []byte 269 var tokenDataBytes []byte 270 var name sql.NullString 271 var author sql.NullString 272 notification := &ActivityCenterNotification{} 273 err := row.Scan( 274 ¬ification.ID, 275 ¬ification.Timestamp, 276 ¬ification.Type, 277 &chatID, 278 &communityID, 279 ¬ification.MembershipStatus, 280 ¬ification.Read, 281 ¬ification.Accepted, 282 ¬ification.Dismissed, 283 ¬ification.Deleted, 284 &messageBytes, 285 &lastMessageBytes, 286 &replyMessageBytes, 287 ¬ification.ContactVerificationStatus, 288 &name, 289 &author, 290 &tokenDataBytes, 291 ¬ification.UpdatedAt) 292 293 if err != nil { 294 return nil, err 295 } 296 297 if chatID.Valid { 298 notification.ChatID = chatID.String 299 } 300 301 if communityID.Valid { 302 notification.CommunityID = communityID.String 303 } 304 305 if name.Valid { 306 notification.Name = name.String 307 } 308 309 if author.Valid { 310 notification.Author = author.String 311 } 312 313 if len(tokenDataBytes) > 0 { 314 err = json.Unmarshal(tokenDataBytes, ¬ification.TokenData) 315 if err != nil { 316 return nil, err 317 } 318 } 319 320 // Restore last message 321 if lastMessageBytes != nil { 322 lastMessage := common.NewMessage() 323 if err = json.Unmarshal(lastMessageBytes, lastMessage); err != nil { 324 return nil, err 325 } 326 notification.LastMessage = lastMessage 327 } 328 329 // Restore message 330 if messageBytes != nil { 331 message := common.NewMessage() 332 if err = json.Unmarshal(messageBytes, message); err != nil { 333 return nil, err 334 } 335 notification.Message = message 336 } 337 338 // Restore reply message 339 if replyMessageBytes != nil { 340 replyMessage := common.NewMessage() 341 if err = json.Unmarshal(replyMessageBytes, replyMessage); err != nil { 342 return nil, err 343 } 344 notification.ReplyMessage = replyMessage 345 } 346 347 return notification, nil 348 } 349 350 func (db sqlitePersistence) unmarshalActivityCenterNotificationRows(rows *sql.Rows) (string, []*ActivityCenterNotification, error) { 351 var notifications []*ActivityCenterNotification 352 latestCursor := "" 353 for rows.Next() { 354 var chatID sql.NullString 355 var communityID sql.NullString 356 var lastMessageBytes []byte 357 var messageBytes []byte 358 var replyMessageBytes []byte 359 var tokenDataBytes []byte 360 var name sql.NullString 361 var author sql.NullString 362 notification := &ActivityCenterNotification{} 363 err := rows.Scan( 364 ¬ification.ID, 365 ¬ification.Timestamp, 366 ¬ification.Type, 367 &chatID, 368 &communityID, 369 ¬ification.MembershipStatus, 370 ¬ification.Read, 371 ¬ification.Accepted, 372 ¬ification.Dismissed, 373 &messageBytes, 374 &lastMessageBytes, 375 &replyMessageBytes, 376 ¬ification.ContactVerificationStatus, 377 &name, 378 &author, 379 &tokenDataBytes, 380 &latestCursor, 381 ¬ification.UpdatedAt) 382 if err != nil { 383 return "", nil, err 384 } 385 386 if chatID.Valid { 387 notification.ChatID = chatID.String 388 } 389 390 if communityID.Valid { 391 notification.CommunityID = communityID.String 392 } 393 394 if name.Valid { 395 notification.Name = name.String 396 } 397 398 if author.Valid { 399 notification.Author = author.String 400 } 401 402 if len(tokenDataBytes) > 0 { 403 tokenData := &ActivityTokenData{} 404 if err = json.Unmarshal(tokenDataBytes, &tokenData); err != nil { 405 return "", nil, err 406 } 407 notification.TokenData = tokenData 408 } 409 410 // Restore last message 411 if lastMessageBytes != nil { 412 lastMessage := common.NewMessage() 413 if err = json.Unmarshal(lastMessageBytes, lastMessage); err != nil { 414 return "", nil, err 415 } 416 notification.LastMessage = lastMessage 417 } 418 419 // Restore message 420 if messageBytes != nil { 421 message := common.NewMessage() 422 if err = json.Unmarshal(messageBytes, message); err != nil { 423 return "", nil, err 424 } 425 notification.Message = message 426 } 427 428 // Restore reply message 429 if replyMessageBytes != nil { 430 replyMessage := common.NewMessage() 431 if err = json.Unmarshal(replyMessageBytes, replyMessage); err != nil { 432 return "", nil, err 433 } 434 notification.ReplyMessage = replyMessage 435 } 436 437 notifications = append(notifications, notification) 438 } 439 440 return latestCursor, notifications, nil 441 442 } 443 444 type activityCenterQueryParams struct { 445 cursor string 446 limit uint64 447 ids []types.HexBytes 448 chatID string 449 author string 450 read ActivityCenterQueryParamsRead 451 accepted bool 452 activityCenterTypes []ActivityCenterType 453 } 454 455 func (db sqlitePersistence) prepareQueryConditionsAndArgs(params activityCenterQueryParams) ([]interface{}, string) { 456 var args []interface{} 457 var conditions []string 458 459 cursor := params.cursor 460 ids := params.ids 461 author := params.author 462 activityCenterTypes := params.activityCenterTypes 463 chatID := params.chatID 464 read := params.read 465 accepted := params.accepted 466 467 if cursor != "" { 468 conditions = append(conditions, "cursor <= ?") 469 args = append(args, cursor) 470 } 471 472 if len(ids) != 0 { 473 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 474 conditions = append(conditions, fmt.Sprintf("a.id IN (%s)", inVector)) 475 for _, id := range ids { 476 args = append(args, id) 477 } 478 } 479 480 switch read { 481 case ActivityCenterQueryParamsReadRead: 482 conditions = append(conditions, "a.read = 1") 483 case ActivityCenterQueryParamsReadUnread: 484 conditions = append(conditions, "NOT a.read") 485 } 486 487 if !accepted { 488 conditions = append(conditions, "NOT a.accepted") 489 } 490 491 if chatID != "" { 492 conditions = append(conditions, "a.chat_id = ?") 493 args = append(args, chatID) 494 } 495 496 if author != "" { 497 conditions = append(conditions, "a.author = ?") 498 args = append(args, author) 499 } 500 501 if len(activityCenterTypes) > 0 { 502 inVector := strings.Repeat("?, ", len(activityCenterTypes)-1) + "?" 503 conditions = append(conditions, fmt.Sprintf("a.notification_type IN (%s)", inVector)) 504 for _, activityCenterType := range activityCenterTypes { 505 args = append(args, activityCenterType) 506 } 507 } 508 509 conditions = append(conditions, "NOT a.deleted") 510 511 var conditionsString string 512 if len(conditions) > 0 { 513 conditionsString = " WHERE " + strings.Join(conditions, " AND ") 514 } 515 516 return args, conditionsString 517 } 518 519 func (db sqlitePersistence) buildActivityCenterQuery(tx *sql.Tx, params activityCenterQueryParams) (string, []*ActivityCenterNotification, error) { 520 args, conditionsString := db.prepareQueryConditionsAndArgs(params) 521 522 query := fmt.Sprintf( // nolint: gosec 523 ` 524 SELECT 525 a.id, 526 a.timestamp, 527 a.notification_type, 528 a.chat_id, 529 a.community_id, 530 a.membership_status, 531 a.read, 532 a.accepted, 533 a.dismissed, 534 a.message, 535 c.last_message, 536 a.reply_message, 537 a.contact_verification_status, 538 c.name, 539 a.author, 540 a.token_data, 541 substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor, 542 a.updated_at 543 FROM activity_center_notifications a 544 LEFT JOIN chats c 545 ON 546 c.id = a.chat_id 547 %s 548 ORDER BY cursor DESC`, conditionsString) 549 550 if params.limit != 0 { 551 args = append(args, params.limit) 552 query += ` LIMIT ?` 553 } 554 555 rows, err := tx.Query(query, args...) 556 if err != nil { 557 return "", nil, err 558 } 559 defer rows.Close() 560 561 return db.unmarshalActivityCenterNotificationRows(rows) 562 } 563 564 func (db sqlitePersistence) buildActivityCenterNotificationsCountQuery(isAccepted bool, read ActivityCenterQueryParamsRead, activityCenterTypes []ActivityCenterType) *sql.Row { 565 params := activityCenterQueryParams{ 566 accepted: isAccepted, 567 read: read, 568 activityCenterTypes: activityCenterTypes, 569 } 570 571 args, conditionsString := db.prepareQueryConditionsAndArgs(params) 572 query := fmt.Sprintf(`SELECT COUNT(1) FROM activity_center_notifications a %s`, conditionsString) 573 574 return db.db.QueryRow(query, args...) 575 } 576 577 func (db sqlitePersistence) runActivityCenterIDQuery(query string) ([][]byte, error) { 578 rows, err := db.db.Query(query) 579 if err != nil { 580 return nil, err 581 } 582 583 var ids [][]byte 584 585 for rows.Next() { 586 var id []byte 587 err = rows.Scan(&id) 588 if err != nil { 589 return nil, err 590 } 591 ids = append(ids, id) 592 } 593 594 return ids, nil 595 } 596 597 func (db sqlitePersistence) GetNotReadActivityCenterNotificationIds() ([][]byte, error) { 598 return db.runActivityCenterIDQuery("SELECT a.id FROM activity_center_notifications a WHERE NOT a.read AND NOT a.deleted") 599 } 600 601 func (db sqlitePersistence) GetToProcessActivityCenterNotificationIds() ([][]byte, error) { 602 return db.runActivityCenterIDQuery(` 603 SELECT a.id 604 FROM activity_center_notifications a 605 WHERE NOT a.dismissed AND NOT a.accepted AND NOT a.deleted 606 `) 607 } 608 609 func (db sqlitePersistence) HasPendingNotificationsForChat(chatID string) (bool, error) { 610 rows, err := db.db.Query(` 611 SELECT 1 FROM activity_center_notifications a 612 WHERE a.chat_id = ? 613 AND NOT a.deleted 614 AND NOT a.dismissed 615 AND NOT a.accepted 616 `, chatID) 617 618 if err != nil { 619 return false, err 620 } 621 622 result := false 623 if rows.Next() { 624 result = true 625 rows.Close() 626 } 627 628 err = rows.Err() 629 return result, err 630 } 631 632 func (db sqlitePersistence) GetActivityCenterNotificationsByID(ids []types.HexBytes) ([]*ActivityCenterNotification, error) { 633 if len(ids) == 0 { 634 return emptyNotifications, nil 635 } 636 idsArgs := make([]interface{}, 0, len(ids)) 637 for _, id := range ids { 638 idsArgs = append(idsArgs, id) 639 } 640 641 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 642 // nolint: gosec 643 rows, err := db.db.Query( 644 ` 645 SELECT 646 a.id, 647 a.timestamp, 648 a.notification_type, 649 a.chat_id, 650 a.community_id, 651 a.membership_status, 652 a.read, 653 a.accepted, 654 a.dismissed, 655 a.message, 656 c.last_message, 657 a.reply_message, 658 a.contact_verification_status, 659 c.name, 660 a.author, 661 a.token_data, 662 substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor, 663 a.updated_at 664 FROM activity_center_notifications a 665 LEFT JOIN chats c 666 ON 667 c.id = a.chat_id 668 WHERE a.id IN (`+inVector+`) AND NOT a.deleted`, idsArgs...) 669 670 if err != nil { 671 return nil, err 672 } 673 674 _, notifications, err := db.unmarshalActivityCenterNotificationRows(rows) 675 if err != nil { 676 return nil, nil 677 } 678 679 return notifications, nil 680 } 681 682 // GetActivityCenterNotificationByID returns a notification by its ID even it's deleted logically 683 func (db sqlitePersistence) GetActivityCenterNotificationByID(id types.HexBytes) (*ActivityCenterNotification, error) { 684 row := db.db.QueryRow(` 685 SELECT 686 a.id, 687 a.timestamp, 688 a.notification_type, 689 a.chat_id, 690 a.community_id, 691 a.membership_status, 692 a.read, 693 a.accepted, 694 a.dismissed, 695 a.deleted, 696 a.message, 697 c.last_message, 698 a.reply_message, 699 a.contact_verification_status, 700 c.name, 701 a.author, 702 a.token_data, 703 a.updated_at 704 FROM activity_center_notifications a 705 LEFT JOIN chats c 706 ON 707 c.id = a.chat_id 708 WHERE a.id = ?`, id) 709 710 notification, err := db.unmarshalActivityCenterNotificationRow(row) 711 if err == sql.ErrNoRows { 712 return nil, nil 713 } 714 return notification, err 715 } 716 717 func (db sqlitePersistence) activityCenterNotifications(params activityCenterQueryParams) (string, []*ActivityCenterNotification, error) { 718 var tx *sql.Tx 719 var err error 720 // We fetch limit + 1 to check for pagination 721 nonIncrementedLimit := params.limit 722 incrementedLimit := int(params.limit) + 1 723 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 724 if err != nil { 725 return "", nil, err 726 } 727 defer func() { 728 if err == nil { 729 err = tx.Commit() 730 return 731 } 732 // don't shadow original error 733 _ = tx.Rollback() 734 }() 735 736 params.limit = uint64(incrementedLimit) 737 latestCursor, notifications, err := db.buildActivityCenterQuery(tx, params) 738 if err != nil { 739 return "", nil, err 740 } 741 742 if len(notifications) == incrementedLimit { 743 notifications = notifications[0:nonIncrementedLimit] 744 } else { 745 latestCursor = "" 746 } 747 748 return latestCursor, notifications, nil 749 } 750 751 func (db sqlitePersistence) DismissAllActivityCenterNotifications(updatedAt uint64) error { 752 _, err := db.db.Exec(`UPDATE activity_center_notifications SET read = 1, dismissed = 1, updated_at = ? WHERE NOT dismissed AND NOT accepted AND NOT deleted`, updatedAt) 753 return err 754 } 755 756 func (db sqlitePersistence) DismissAllActivityCenterNotificationsFromUser(userPublicKey string, updatedAt uint64) ([]*ActivityCenterNotification, error) { 757 var tx *sql.Tx 758 var err error 759 760 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 761 if err != nil { 762 return nil, err 763 } 764 defer func() { 765 if err == nil { 766 err = tx.Commit() 767 return 768 } 769 // don't shadow original error 770 _ = tx.Rollback() 771 }() 772 773 query := fmt.Sprintf(`SELECT %s FROM activity_center_notifications WHERE 774 author = ? AND 775 NOT deleted AND 776 NOT dismissed AND 777 NOT accepted`, allFieldsForTableActivityCenterNotification) 778 rows, err := tx.Query(query, userPublicKey) 779 if err != nil { 780 return nil, err 781 } 782 var notifications []*ActivityCenterNotification 783 notifications, err = db.parseRowFromTableActivityCenterNotification(rows, func(notification *ActivityCenterNotification) { 784 notification.Read = true 785 notification.Dismissed = true 786 notification.UpdatedAt = updatedAt 787 }) 788 if err != nil { 789 return nil, err 790 } 791 792 _, err = tx.Exec(` 793 UPDATE activity_center_notifications 794 SET read = 1, dismissed = 1, updated_at = ? 795 WHERE author = ? 796 AND NOT deleted 797 AND NOT dismissed 798 AND NOT accepted 799 `, 800 updatedAt, userPublicKey) 801 if err != nil { 802 return nil, err 803 } 804 805 return notifications, updateActivityCenterState(tx, updatedAt) 806 } 807 808 func (db sqlitePersistence) MarkActivityCenterNotificationsDeleted(ids []types.HexBytes, updatedAt uint64) ([]*ActivityCenterNotification, error) { 809 if len(ids) == 0 { 810 return emptyNotifications, nil 811 } 812 813 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 814 args := make([]interface{}, 0, len(ids)+1) 815 args = append(args, updatedAt) 816 for _, id := range ids { 817 args = append(args, id) 818 } 819 820 var tx *sql.Tx 821 var err error 822 823 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 824 if err != nil { 825 return nil, err 826 } 827 defer func() { 828 if err == nil { 829 err = tx.Commit() 830 return 831 } 832 // don't shadow original error 833 _ = tx.Rollback() 834 }() 835 836 // nolint: gosec 837 query := fmt.Sprintf(`SELECT %s FROM activity_center_notifications WHERE id IN (%s) AND NOT deleted`, 838 allFieldsForTableActivityCenterNotification, 839 inVector) 840 rows, err := tx.Query(query, args[1:]...) 841 if err != nil { 842 return nil, err 843 } 844 notifications, err := db.parseRowFromTableActivityCenterNotification(rows, func(notification *ActivityCenterNotification) { 845 notification.Deleted = true 846 notification.UpdatedAt = updatedAt 847 }) 848 if err != nil { 849 return nil, err 850 } 851 852 update := "UPDATE activity_center_notifications SET deleted = 1, updated_at = ? WHERE id IN (" + inVector + ") AND NOT deleted" //nolint: gosec 853 _, err = tx.Exec(update, args...) 854 if err != nil { 855 return nil, err 856 } 857 858 return notifications, updateActivityCenterState(tx, updatedAt) 859 } 860 861 func (db sqlitePersistence) DismissActivityCenterNotifications(ids []types.HexBytes, updatedAt uint64) error { 862 if len(ids) == 0 { 863 return nil 864 } 865 866 args := make([]interface{}, 0, len(ids)+1) 867 args = append(args, updatedAt) 868 for _, id := range ids { 869 args = append(args, id) 870 } 871 872 var tx *sql.Tx 873 var err error 874 875 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 876 if err != nil { 877 return err 878 } 879 defer func() { 880 if err == nil { 881 err = tx.Commit() 882 return 883 } 884 // don't shadow original error 885 _ = tx.Rollback() 886 }() 887 888 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 889 query := "UPDATE activity_center_notifications SET read = 1, dismissed = 1, updated_at = ? WHERE id IN (" + inVector + ") AND not deleted" // nolint: gosec 890 _, err = tx.Exec(query, args...) 891 if err != nil { 892 return err 893 } 894 return updateActivityCenterState(tx, updatedAt) 895 } 896 897 func (db sqlitePersistence) DismissActivityCenterNotificationsByCommunity(communityID string, updatedAt uint64) ([]*ActivityCenterNotification, error) { 898 var tx *sql.Tx 899 var err error 900 901 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 902 if err != nil { 903 return nil, err 904 } 905 defer func() { 906 if err == nil { 907 err = tx.Commit() 908 return 909 } 910 // don't shadow original error 911 _ = tx.Rollback() 912 }() 913 914 query := "UPDATE activity_center_notifications SET read = 1, dismissed = 1, updated_at = ? WHERE community_id = ? AND notification_type IN (?, ?, ?, ?) AND NOT deleted" // nolint: gosec 915 _, err = tx.Exec(query, updatedAt, communityID, 916 ActivityCenterNotificationTypeCommunityRequest, ActivityCenterNotificationTypeCommunityKicked, ActivityCenterNotificationTypeCommunityBanned, ActivityCenterNotificationTypeCommunityUnbanned) 917 if err != nil { 918 return nil, err 919 } 920 921 _, notifications, err := db.buildActivityCenterQuery(tx, activityCenterQueryParams{}) 922 if err != nil { 923 return nil, err 924 } 925 926 return notifications, updateActivityCenterState(tx, updatedAt) 927 } 928 929 func (db sqlitePersistence) DismissAllActivityCenterNotificationsFromCommunity(communityID string, updatedAt uint64) ([]*ActivityCenterNotification, error) { 930 var tx *sql.Tx 931 var err error 932 933 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 934 if err != nil { 935 return nil, err 936 } 937 defer func() { 938 if err == nil { 939 err = tx.Commit() 940 return 941 } 942 // don't shadow original error 943 _ = tx.Rollback() 944 }() 945 946 chatIDs, err := db.AllChatIDsByCommunity(tx, communityID) 947 if err != nil { 948 return nil, err 949 } 950 951 chatIDsCount := len(chatIDs) 952 if chatIDsCount == 0 { 953 return nil, nil 954 } 955 956 args := make([]interface{}, 0, chatIDsCount+1) 957 args = append(args, updatedAt) 958 for _, chatID := range chatIDs { 959 args = append(args, chatID) 960 } 961 962 inVector := strings.Repeat("?, ", chatIDsCount-1) + "?" 963 964 // nolint: gosec 965 query := fmt.Sprintf(`SELECT %s FROM activity_center_notifications WHERE chat_id IN (%s) AND NOT deleted`, allFieldsForTableActivityCenterNotification, inVector) 966 rows, err := tx.Query(query, args[1:]...) 967 if err != nil { 968 return nil, err 969 } 970 notifications, err := db.parseRowFromTableActivityCenterNotification(rows, func(notification *ActivityCenterNotification) { 971 notification.Read = true 972 notification.Dismissed = true 973 notification.UpdatedAt = updatedAt 974 }) 975 if err != nil { 976 return nil, err 977 } 978 979 query = "UPDATE activity_center_notifications SET read = 1, dismissed = 1, updated_at = ? WHERE chat_id IN (" + inVector + ") AND NOT deleted" // nolint: gosec 980 _, err = tx.Exec(query, args...) 981 if err != nil { 982 return nil, err 983 } 984 return notifications, updateActivityCenterState(tx, updatedAt) 985 } 986 987 func (db sqlitePersistence) DismissAllActivityCenterNotificationsFromChatID(chatID string, updatedAt uint64) ([]*ActivityCenterNotification, error) { 988 var tx *sql.Tx 989 var err error 990 991 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 992 if err != nil { 993 return nil, err 994 } 995 defer func() { 996 if err == nil { 997 err = tx.Commit() 998 return 999 } 1000 // don't shadow original error 1001 _ = tx.Rollback() 1002 }() 1003 1004 query := fmt.Sprintf(`SELECT %s FROM activity_center_notifications 1005 WHERE chat_id = ? 1006 AND NOT deleted 1007 AND NOT accepted 1008 AND notification_type != ?`, allFieldsForTableActivityCenterNotification) 1009 rows, err := tx.Query(query, chatID, ActivityCenterNotificationTypeContactRequest) 1010 if err != nil { 1011 return nil, err 1012 } 1013 notifications, err := db.parseRowFromTableActivityCenterNotification(rows, func(notification *ActivityCenterNotification) { 1014 notification.Read = true 1015 notification.Dismissed = true 1016 notification.UpdatedAt = updatedAt 1017 }) 1018 if err != nil { 1019 return nil, err 1020 } 1021 1022 // We exclude notifications related to contacts, since those we don't want to 1023 // be cleared. 1024 query = ` 1025 UPDATE activity_center_notifications 1026 SET read = 1, dismissed = 1, updated_at = ? 1027 WHERE chat_id = ? 1028 AND NOT deleted 1029 AND NOT accepted 1030 AND notification_type != ? 1031 ` 1032 _, err = tx.Exec(query, updatedAt, chatID, ActivityCenterNotificationTypeContactRequest) 1033 if err != nil { 1034 return nil, err 1035 } 1036 return notifications, updateActivityCenterState(tx, updatedAt) 1037 } 1038 1039 func (db sqlitePersistence) AcceptAllActivityCenterNotifications(updatedAt uint64) ([]*ActivityCenterNotification, error) { 1040 var tx *sql.Tx 1041 var err error 1042 1043 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1044 if err != nil { 1045 return nil, err 1046 } 1047 defer func() { 1048 if err == nil { 1049 err = tx.Commit() 1050 return 1051 } 1052 // don't shadow original error 1053 _ = tx.Rollback() 1054 }() 1055 1056 _, notifications, err := db.buildActivityCenterQuery(tx, activityCenterQueryParams{}) 1057 if err != nil { 1058 return nil, err 1059 } 1060 1061 _, err = tx.Exec(`UPDATE activity_center_notifications SET read = 1, accepted = 1, updated_at = ? WHERE NOT dismissed AND NOT accepted AND NOT deleted`, updatedAt) 1062 return notifications, err 1063 } 1064 1065 func (db sqlitePersistence) AcceptActivityCenterNotifications(ids []types.HexBytes, updatedAt uint64) ([]*ActivityCenterNotification, error) { 1066 if len(ids) == 0 { 1067 return emptyNotifications, nil 1068 } 1069 var tx *sql.Tx 1070 var err error 1071 1072 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1073 if err != nil { 1074 return nil, err 1075 } 1076 defer func() { 1077 if err == nil { 1078 err = tx.Commit() 1079 return 1080 } 1081 // don't shadow original error 1082 _ = tx.Rollback() 1083 }() 1084 1085 args := make([]interface{}, 0, len(ids)+1) 1086 args = append(args, updatedAt) 1087 for _, id := range ids { 1088 args = append(args, id) 1089 } 1090 1091 params := activityCenterQueryParams{ 1092 ids: ids, 1093 } 1094 _, notifications, err := db.buildActivityCenterQuery(tx, params) 1095 if err != nil { 1096 return nil, err 1097 } 1098 var updateNotifications []*ActivityCenterNotification 1099 for _, n := range notifications { 1100 n.Read = true 1101 n.Accepted = true 1102 n.UpdatedAt = updatedAt 1103 updateNotifications = append(updateNotifications, n) 1104 } 1105 1106 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 1107 query := "UPDATE activity_center_notifications SET read = 1, accepted = 1, updated_at = ? WHERE id IN (" + inVector + ") AND NOT deleted" // nolint: gosec 1108 _, err = tx.Exec(query, args...) 1109 if err != nil { 1110 return nil, err 1111 } 1112 1113 return updateNotifications, updateActivityCenterState(tx, updatedAt) 1114 } 1115 1116 func (db sqlitePersistence) AcceptActivityCenterNotificationsForInvitesFromUser(userPublicKey string, updatedAt uint64) ([]*ActivityCenterNotification, error) { 1117 var tx *sql.Tx 1118 var err error 1119 1120 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1121 if err != nil { 1122 return nil, err 1123 } 1124 defer func() { 1125 if err == nil { 1126 err = tx.Commit() 1127 return 1128 } 1129 // don't shadow original error 1130 _ = tx.Rollback() 1131 }() 1132 1133 query := fmt.Sprintf(`SELECT %s FROM activity_center_notifications 1134 WHERE author = ? 1135 AND NOT deleted 1136 AND NOT dismissed 1137 AND NOT accepted 1138 AND notification_type = ?`, allFieldsForTableActivityCenterNotification) 1139 rows, err := tx.Query(query, userPublicKey, ActivityCenterNotificationTypeNewPrivateGroupChat) 1140 if err != nil { 1141 return nil, err 1142 } 1143 notifications, err := db.parseRowFromTableActivityCenterNotification(rows, func(notification *ActivityCenterNotification) { 1144 notification.Read = true 1145 notification.Accepted = true 1146 notification.UpdatedAt = updatedAt 1147 }) 1148 if err != nil { 1149 return nil, err 1150 } 1151 1152 _, err = tx.Exec(` 1153 UPDATE activity_center_notifications 1154 SET read = 1, accepted = 1, updated_at = ? 1155 WHERE author = ? 1156 AND NOT deleted 1157 AND NOT dismissed 1158 AND NOT accepted 1159 AND notification_type = ? 1160 `, 1161 updatedAt, userPublicKey, ActivityCenterNotificationTypeNewPrivateGroupChat) 1162 1163 if err != nil { 1164 return nil, err 1165 } 1166 1167 return notifications, updateActivityCenterState(tx, updatedAt) 1168 } 1169 1170 func (db sqlitePersistence) MarkAllActivityCenterNotificationsRead(updatedAt uint64) error { 1171 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1172 if err != nil { 1173 return err 1174 } 1175 defer func() { 1176 if err == nil { 1177 err = tx.Commit() 1178 return 1179 } 1180 // don't shadow original error 1181 _ = tx.Rollback() 1182 }() 1183 _, err = tx.Exec(`UPDATE activity_center_notifications SET read = 1, updated_at = ? WHERE NOT read AND NOT deleted`, updatedAt) 1184 if err != nil { 1185 return err 1186 } 1187 _, err = tx.Exec(`UPDATE activity_center_states SET has_seen = 1, updated_at = ?`, updatedAt) 1188 return err 1189 } 1190 1191 func (db sqlitePersistence) MarkActivityCenterNotificationsRead(ids []types.HexBytes, updatedAt uint64) error { 1192 if len(ids) == 0 { 1193 return nil 1194 } 1195 args := make([]interface{}, 0, len(ids)+1) 1196 args = append(args, updatedAt) 1197 for _, id := range ids { 1198 args = append(args, id) 1199 } 1200 1201 var tx *sql.Tx 1202 var err error 1203 1204 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1205 if err != nil { 1206 return err 1207 } 1208 defer func() { 1209 if err == nil { 1210 err = tx.Commit() 1211 return 1212 } 1213 // don't shadow original error 1214 _ = tx.Rollback() 1215 }() 1216 1217 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 1218 query := "UPDATE activity_center_notifications SET read = 1, updated_at = ? WHERE id IN (" + inVector + ") AND NOT deleted" // nolint: gosec 1219 _, err = tx.Exec(query, args...) 1220 if err != nil { 1221 return err 1222 } 1223 return updateActivityCenterState(tx, updatedAt) 1224 1225 } 1226 1227 func updateActivityCenterState(tx *sql.Tx, updatedAt uint64) error { 1228 var unreadCount int 1229 err := tx.QueryRow("SELECT COUNT(1) FROM activity_center_notifications WHERE read = 0 AND deleted = 0").Scan(&unreadCount) 1230 if err != nil { 1231 return err 1232 } 1233 var hasSeen int 1234 if unreadCount == 0 { 1235 hasSeen = 1 1236 } 1237 1238 _, err = tx.Exec(`UPDATE activity_center_states SET has_seen = ?, updated_at = ?`, hasSeen, updatedAt) 1239 return err 1240 } 1241 1242 func (db sqlitePersistence) MarkActivityCenterNotificationsUnread(ids []types.HexBytes, updatedAt uint64) ([]*ActivityCenterNotification, error) { 1243 if len(ids) == 0 { 1244 return emptyNotifications, nil 1245 } 1246 args := make([]interface{}, 0, len(ids)+1) 1247 args = append(args, updatedAt) 1248 for _, id := range ids { 1249 args = append(args, id) 1250 } 1251 1252 inVector := strings.Repeat("?, ", len(ids)-1) + "?" 1253 // nolint: gosec 1254 query := fmt.Sprintf("SELECT %s FROM activity_center_notifications WHERE id IN (%s) AND NOT deleted", allFieldsForTableActivityCenterNotification, inVector) 1255 1256 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 1257 if err != nil { 1258 return nil, err 1259 } 1260 defer func() { 1261 if err == nil { 1262 err = tx.Commit() 1263 return 1264 } 1265 // don't shadow original error 1266 _ = tx.Rollback() 1267 }() 1268 1269 rows, err := tx.Query(query, args[1:]...) 1270 if err != nil { 1271 return nil, err 1272 } 1273 notifications, err := db.parseRowFromTableActivityCenterNotification(rows, func(notification *ActivityCenterNotification) { 1274 notification.Read = false 1275 notification.UpdatedAt = updatedAt 1276 }) 1277 if err != nil { 1278 return nil, err 1279 } 1280 1281 if len(notifications) == 0 { 1282 return notifications, nil 1283 } 1284 1285 query = "UPDATE activity_center_notifications SET read = 0, updated_at = ? WHERE id IN (" + inVector + ") AND NOT deleted" // nolint: gosec 1286 _, err = tx.Exec(query, args...) 1287 if err != nil { 1288 return nil, err 1289 } 1290 _, err = tx.Exec(`UPDATE activity_center_states SET has_seen = 0, updated_at = ?`, updatedAt) 1291 return notifications, err 1292 } 1293 1294 func (db sqlitePersistence) ActivityCenterNotifications(cursor string, limit uint64, activityTypes []ActivityCenterType, readType ActivityCenterQueryParamsRead, accepted bool) (string, []*ActivityCenterNotification, error) { 1295 params := activityCenterQueryParams{ 1296 activityCenterTypes: activityTypes, 1297 cursor: cursor, 1298 limit: limit, 1299 read: readType, 1300 accepted: accepted, 1301 } 1302 1303 return db.activityCenterNotifications(params) 1304 } 1305 1306 func (db sqlitePersistence) ActivityCenterNotificationsCount(activityTypes []ActivityCenterType, readType ActivityCenterQueryParamsRead, accepted bool) (uint64, error) { 1307 var count uint64 1308 err := db.buildActivityCenterNotificationsCountQuery(accepted, readType, activityTypes).Scan(&count) 1309 return count, err 1310 } 1311 1312 func (db sqlitePersistence) ActiveContactRequestNotification(contactID string) (*ActivityCenterNotification, error) { 1313 // QueryRow expects a query that returns at most one row. In theory the query 1314 // wouldn't even need the ORDER + LIMIT 1 because we expect only one active 1315 // contact request per contact, but to avoid relying on the unpredictable 1316 // behavior of the DB engine for sorting, we sort by notification.Timestamp 1317 // DESC. 1318 query := ` 1319 SELECT 1320 a.id, 1321 a.timestamp, 1322 a.notification_type, 1323 a.chat_id, 1324 a.community_id, 1325 a.membership_status, 1326 a.read, 1327 a.accepted, 1328 a.dismissed, 1329 a.deleted, 1330 a.message, 1331 c.last_message, 1332 a.reply_message, 1333 a.contact_verification_status, 1334 c.name, 1335 a.author, 1336 a.token_data, 1337 a.updated_at 1338 FROM activity_center_notifications a 1339 LEFT JOIN chats c ON c.id = a.chat_id 1340 WHERE a.author = ? 1341 AND NOT a.deleted 1342 AND NOT a.dismissed 1343 AND NOT a.accepted 1344 AND a.notification_type = ? 1345 ORDER BY a.timestamp DESC 1346 LIMIT 1 1347 ` 1348 row := db.db.QueryRow(query, contactID, ActivityCenterNotificationTypeContactRequest) 1349 notification, err := db.unmarshalActivityCenterNotificationRow(row) 1350 if err == sql.ErrNoRows { 1351 return nil, nil 1352 } 1353 return notification, err 1354 } 1355 1356 func (db sqlitePersistence) DeleteChatContactRequestActivityCenterNotifications(chatID string, updatedAt uint64) ([]*ActivityCenterNotification, error) { 1357 query := fmt.Sprintf(`SELECT %s FROM activity_center_notifications WHERE chat_id = ? AND NOT deleted AND notification_type = ?`, allFieldsForTableActivityCenterNotification) 1358 rows, err := db.db.Query(query, chatID, ActivityCenterNotificationTypeContactRequest) 1359 if err != nil { 1360 return nil, err 1361 } 1362 notifications, err := db.parseRowFromTableActivityCenterNotification(rows, func(notification *ActivityCenterNotification) { 1363 notification.Deleted = true 1364 notification.UpdatedAt = updatedAt 1365 }) 1366 if err != nil { 1367 return nil, err 1368 } 1369 1370 _, err = db.db.Exec(` 1371 UPDATE activity_center_notifications SET deleted = 1, updated_at = ? 1372 WHERE 1373 chat_id = ? 1374 AND NOT deleted 1375 AND notification_type = ? 1376 `, updatedAt, chatID, ActivityCenterNotificationTypeContactRequest) 1377 return notifications, err 1378 } 1379 1380 func (db sqlitePersistence) HasUnseenActivityCenterNotifications() (bool, uint64, error) { 1381 row := db.db.QueryRow(`SELECT has_seen, updated_at FROM activity_center_states`) 1382 hasSeen := true 1383 updatedAt := uint64(0) 1384 err := row.Scan(&hasSeen, &updatedAt) 1385 return !hasSeen, updatedAt, err 1386 } 1387 1388 func (db sqlitePersistence) UpdateActivityCenterNotificationState(state *ActivityCenterState) (int64, error) { 1389 result, err := db.db.Exec(`UPDATE activity_center_states SET has_seen = ?, updated_at = ?`, state.HasSeen, state.UpdatedAt) 1390 if err != nil { 1391 return 0, err 1392 } 1393 return result.RowsAffected() 1394 } 1395 1396 func (db sqlitePersistence) GetActivityCenterState() (*ActivityCenterState, error) { 1397 unseen, updatedAt, err := db.HasUnseenActivityCenterNotifications() 1398 if err != nil { 1399 return nil, err 1400 } 1401 1402 state := &ActivityCenterState{ 1403 HasSeen: !unseen, 1404 UpdatedAt: updatedAt, 1405 } 1406 return state, nil 1407 } 1408 1409 func (db sqlitePersistence) UpdateActivityCenterState(updatedAt uint64) (*ActivityCenterState, error) { 1410 var unreadCount int 1411 err := db.db.QueryRow("SELECT COUNT(1) FROM activity_center_notifications WHERE read = 0 AND deleted = 0").Scan(&unreadCount) 1412 if err != nil { 1413 return nil, err 1414 } 1415 var hasSeen int 1416 if unreadCount == 0 { 1417 hasSeen = 1 1418 } 1419 1420 _, err = db.db.Exec(`UPDATE activity_center_states SET has_seen = ?, updated_at = ?`, hasSeen, updatedAt) 1421 return &ActivityCenterState{HasSeen: hasSeen == 1, UpdatedAt: updatedAt}, err 1422 }