status-go/services/wallet/saved_addresses.go
Sale Djenic 280f48877d chore(savedaddresses)!: favourite property removed and primary key updated
- favourite column removed from the saved_addresses table
- favourite property removed from the SavedAddress struct
- ens name removed from the primary key, the primary key now is composed of address and is_test columns
- ens parameter removed from wakuext_deleteSavedAddress
- wallet_getSavedAddresses moved to wakuext_getSavedAddresses (to keep them all in a single place)
- saved addresses related endpoints removed from the wallet service, even they logically belong there, a reason for that
is avoiding emitting sync message if one uses calls from the wallet service, while that's not the case in ext service. Once
we refactor this and introduce devices syncing mechanism in the wallet service, we should not only these but other wallet
related endpoints move there (removed: wallet_getSavedAddresses, wallet_addSavedAddress and wallet_deleteSavedAddress).

Affected area:
Saved addresses
2024-01-10 19:30:56 +01:00

243 lines
6.1 KiB
Go

package wallet
import (
"database/sql"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
multiAccCommon "github.com/status-im/status-go/multiaccounts/common"
)
type savedAddressMeta struct {
Removed bool
UpdateClock uint64 // wall clock used to deconflict concurrent updates
}
type SavedAddress struct {
Address common.Address `json:"address"`
// TODO: Add Emoji
// Emoji string `json:"emoji"`
Name string `json:"name"`
ChainShortNames string `json:"chainShortNames"` // used with address only, not with ENSName
ENSName string `json:"ens"`
ColorID multiAccCommon.CustomizationColor `json:"colorId"`
IsTest bool `json:"isTest"`
CreatedAt int64 `json:"createdAt"`
savedAddressMeta
}
func (s *SavedAddress) ID() string {
return fmt.Sprintf("%s-%t", s.Address.Hex(), s.IsTest)
}
type SavedAddressesManager struct {
db *sql.DB
}
func NewSavedAddressesManager(db *sql.DB) *SavedAddressesManager {
return &SavedAddressesManager{db: db}
}
const rawQueryColumnsOrder = "address, name, removed, update_clock, chain_short_names, ens_name, is_test, created_at, color"
// getSavedAddressesFromDBRows retrieves all data based on SELECT Query using rawQueryColumnsOrder
func getSavedAddressesFromDBRows(rows *sql.Rows) ([]SavedAddress, error) {
var addresses []SavedAddress
for rows.Next() {
sa := SavedAddress{}
// based on rawQueryColumnsOrder
err := rows.Scan(
&sa.Address,
&sa.Name,
&sa.Removed,
&sa.UpdateClock,
&sa.ChainShortNames,
&sa.ENSName,
&sa.IsTest,
&sa.CreatedAt,
&sa.ColorID,
)
if err != nil {
return nil, err
}
addresses = append(addresses, sa)
}
return addresses, nil
}
func (sam *SavedAddressesManager) getSavedAddresses(condition string) ([]SavedAddress, error) {
var whereCondition string
if condition != "" {
whereCondition = fmt.Sprintf("WHERE %s", condition)
}
rows, err := sam.db.Query(fmt.Sprintf("SELECT %s FROM saved_addresses %s", rawQueryColumnsOrder, whereCondition))
if err != nil {
return nil, err
}
defer rows.Close()
addresses, err := getSavedAddressesFromDBRows(rows)
return addresses, err
}
func (sam *SavedAddressesManager) GetSavedAddresses() ([]SavedAddress, error) {
return sam.getSavedAddresses("removed != 1")
}
// GetRawSavedAddresses provides access to the soft-delete and sync metadata
func (sam *SavedAddressesManager) GetRawSavedAddresses() ([]SavedAddress, error) {
return sam.getSavedAddresses("")
}
func (sam *SavedAddressesManager) upsertSavedAddress(sa SavedAddress, tx *sql.Tx) (err error) {
if tx == nil {
tx, err = sam.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
}
rows, err := tx.Query(
fmt.Sprintf("SELECT %s FROM saved_addresses WHERE address = ? AND is_test = ?", rawQueryColumnsOrder),
sa.Address, sa.IsTest,
)
if err != nil {
return err
}
defer rows.Close()
savedAddresses, err := getSavedAddressesFromDBRows(rows)
if err != nil {
return err
}
sa.CreatedAt = time.Now().Unix()
for _, savedAddress := range savedAddresses {
if savedAddress.Address == sa.Address && savedAddress.IsTest == sa.IsTest {
sa.CreatedAt = savedAddress.CreatedAt
break
}
}
sqlStatement := `
INSERT OR REPLACE
INTO
saved_addresses (
address,
name,
removed,
update_clock,
chain_short_names,
ens_name,
is_test,
created_at,
color
)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?)
`
insert, err := tx.Prepare(sqlStatement)
if err != nil {
return err
}
defer insert.Close()
_, err = insert.Exec(sa.Address, sa.Name, sa.Removed, sa.UpdateClock, sa.ChainShortNames, sa.ENSName,
sa.IsTest, sa.CreatedAt, sa.ColorID)
return err
}
func (sam *SavedAddressesManager) UpdateMetadataAndUpsertSavedAddress(sa SavedAddress) (updatedClock uint64, err error) {
sa.UpdateClock = uint64(time.Now().Unix())
err = sam.upsertSavedAddress(sa, nil)
if err != nil {
return 0, err
}
return sa.UpdateClock, nil
}
func (sam *SavedAddressesManager) startTransactionAndCheckIfNewerChange(address common.Address, isTest bool, updateClock uint64) (newer bool, tx *sql.Tx, err error) {
tx, err = sam.db.Begin()
if err != nil {
return false, nil, err
}
row := tx.QueryRow("SELECT update_clock FROM saved_addresses WHERE address = ? AND is_test = ?", address, isTest)
if err != nil {
return false, tx, err
}
var dbUpdateClock uint64
err = row.Scan(&dbUpdateClock)
if err != nil {
return err == sql.ErrNoRows, tx, err
}
return dbUpdateClock < updateClock, tx, nil
}
func (sam *SavedAddressesManager) AddSavedAddressIfNewerUpdate(sa SavedAddress, updateClock uint64) (insertedOrUpdated bool, err error) {
newer, tx, err := sam.startTransactionAndCheckIfNewerChange(sa.Address, sa.IsTest, updateClock)
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
if !newer {
return false, err
}
sa.UpdateClock = updateClock
err = sam.upsertSavedAddress(sa, tx)
if err != nil {
return false, err
}
return true, err
}
func (sam *SavedAddressesManager) DeleteSavedAddress(address common.Address, isTest bool, updateClock uint64) (deleted bool, err error) {
if err != nil {
return false, err
}
newer, tx, err := sam.startTransactionAndCheckIfNewerChange(address, isTest, updateClock)
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
if !newer {
return false, err
}
update, err := tx.Prepare(`UPDATE saved_addresses SET removed = 1, update_clock = ? WHERE address = ? AND is_test = ?`)
if err != nil {
return false, err
}
defer update.Close()
res, err := update.Exec(updateClock, address, isTest)
if err != nil {
return false, err
}
nRows, err := res.RowsAffected()
if err != nil {
return false, err
}
return nRows > 0, nil
}
func (sam *SavedAddressesManager) DeleteSoftRemovedSavedAddresses(threshold uint64) error {
_, err := sam.db.Exec(`DELETE FROM saved_addresses WHERE removed = 1 AND update_clock < ?`, threshold)
return err
}