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  			&notification.ID,
   201  			&notification.Timestamp,
   202  			&notification.Type,
   203  			&chatID,
   204  			&notification.Read,
   205  			&notification.Dismissed,
   206  			&notification.Accepted,
   207  			&messageBytes,
   208  			&author,
   209  			&replyMessageBytes,
   210  			&communityID,
   211  			&notification.MembershipStatus,
   212  			&notification.ContactVerificationStatus,
   213  			&tokenDataBytes,
   214  			&notification.Deleted,
   215  			&notification.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, &notification.TokenData)
   235  			if err != nil {
   236  				return nil, err
   237  			}
   238  		}
   239  
   240  		if len(messageBytes) > 0 {
   241  			err = json.Unmarshal(messageBytes, &notification.Message)
   242  			if err != nil {
   243  				return nil, err
   244  			}
   245  		}
   246  
   247  		if len(replyMessageBytes) > 0 {
   248  			err = json.Unmarshal(replyMessageBytes, &notification.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  		&notification.ID,
   275  		&notification.Timestamp,
   276  		&notification.Type,
   277  		&chatID,
   278  		&communityID,
   279  		&notification.MembershipStatus,
   280  		&notification.Read,
   281  		&notification.Accepted,
   282  		&notification.Dismissed,
   283  		&notification.Deleted,
   284  		&messageBytes,
   285  		&lastMessageBytes,
   286  		&replyMessageBytes,
   287  		&notification.ContactVerificationStatus,
   288  		&name,
   289  		&author,
   290  		&tokenDataBytes,
   291  		&notification.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, &notification.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  			&notification.ID,
   365  			&notification.Timestamp,
   366  			&notification.Type,
   367  			&chatID,
   368  			&communityID,
   369  			&notification.MembershipStatus,
   370  			&notification.Read,
   371  			&notification.Accepted,
   372  			&notification.Dismissed,
   373  			&messageBytes,
   374  			&lastMessageBytes,
   375  			&replyMessageBytes,
   376  			&notification.ContactVerificationStatus,
   377  			&name,
   378  			&author,
   379  			&tokenDataBytes,
   380  			&latestCursor,
   381  			&notification.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  }