feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
package accounts
const (
// NodeConfigTag tag for a node configuration.
NodeConfigTag = "node_config"
)

View File

@@ -0,0 +1,477 @@
package accounts
import (
"database/sql"
"errors"
"fmt"
"strings"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/protobuf"
)
var (
errKeycardDbTransactionIsNil = errors.New("keycard: database transaction is nil")
errCannotAddKeycardForUnknownKeypair = errors.New("keycard: cannot add keycard for an unknown keyapir")
ErrNoKeycardForPassedKeycardUID = errors.New("keycard: no keycard for the passed keycard uid")
)
type Keycard struct {
KeycardUID string `json:"keycard-uid"`
KeycardName string `json:"keycard-name"`
KeycardLocked bool `json:"keycard-locked"`
AccountsAddresses []types.Address `json:"accounts-addresses"`
KeyUID string `json:"key-uid"`
Position uint64
}
func (kp *Keycard) ToSyncKeycard() *protobuf.SyncKeycard {
kc := &protobuf.SyncKeycard{
Uid: kp.KeycardUID,
Name: kp.KeycardName,
Locked: kp.KeycardLocked,
KeyUid: kp.KeyUID,
Position: kp.Position,
}
for _, addr := range kp.AccountsAddresses {
kc.Addresses = append(kc.Addresses, addr.Bytes())
}
return kc
}
func (kp *Keycard) FromSyncKeycard(kc *protobuf.SyncKeycard) {
kp.KeycardUID = kc.Uid
kp.KeycardName = kc.Name
kp.KeycardLocked = kc.Locked
kp.KeyUID = kc.KeyUid
kp.Position = kc.Position
for _, addr := range kc.Addresses {
kp.AccountsAddresses = append(kp.AccountsAddresses, types.BytesToAddress(addr))
}
}
func containsAddress(addresses []types.Address, address types.Address) bool {
for _, addr := range addresses {
if addr == address {
return true
}
}
return false
}
func (db *Database) processResult(rows *sql.Rows) ([]*Keycard, error) {
keycards := []*Keycard{}
for rows.Next() {
keycard := &Keycard{}
var accAddress sql.NullString
err := rows.Scan(&keycard.KeycardUID, &keycard.KeycardName, &keycard.KeycardLocked, &accAddress, &keycard.KeyUID,
&keycard.Position)
if err != nil {
return nil, err
}
addr := types.Address{}
if accAddress.Valid {
addr = types.BytesToAddress([]byte(accAddress.String))
}
foundAtIndex := -1
for i := range keycards {
if keycards[i].KeycardUID == keycard.KeycardUID {
foundAtIndex = i
break
}
}
if foundAtIndex == -1 {
keycard.AccountsAddresses = append(keycard.AccountsAddresses, addr)
keycards = append(keycards, keycard)
} else {
if containsAddress(keycards[foundAtIndex].AccountsAddresses, addr) {
continue
}
keycards[foundAtIndex].AccountsAddresses = append(keycards[foundAtIndex].AccountsAddresses, addr)
}
}
return keycards, nil
}
func (db *Database) getKeycards(tx *sql.Tx, keyUID string, keycardUID string) ([]*Keycard, error) {
query := `
SELECT
kc.keycard_uid,
kc.keycard_name,
kc.keycard_locked,
ka.account_address,
kc.key_uid,
kc.position
FROM
keycards AS kc
LEFT JOIN
keycards_accounts AS ka
ON
kc.keycard_uid = ka.keycard_uid
LEFT JOIN
keypairs_accounts AS kpa
ON
ka.account_address = kpa.address
%s
ORDER BY
kc.position, kpa.position`
var where string
var args []interface{}
if keyUID != "" {
where = "WHERE kc.key_uid = ?"
args = append(args, keyUID)
if keycardUID != "" {
where += " AND kc.keycard_uid = ?"
args = append(args, keycardUID)
}
} else if keycardUID != "" {
where = "WHERE kc.keycard_uid = ?"
args = append(args, keycardUID)
}
query = fmt.Sprintf(query, where)
var (
stmt *sql.Stmt
err error
)
if tx == nil {
stmt, err = db.db.Prepare(query)
} else {
stmt, err = tx.Prepare(query)
}
if err != nil {
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, err
}
defer rows.Close()
return db.processResult(rows)
}
func (db *Database) getKeycardByKeycardUID(tx *sql.Tx, keycardUID string) (*Keycard, error) {
keycards, err := db.getKeycards(tx, "", keycardUID)
if err != nil {
return nil, err
}
if len(keycards) == 0 {
return nil, ErrNoKeycardForPassedKeycardUID
}
return keycards[0], nil
}
func (db *Database) GetAllKnownKeycards() ([]*Keycard, error) {
return db.getKeycards(nil, "", "")
}
func (db *Database) GetKeycardsWithSameKeyUID(keyUID string) ([]*Keycard, error) {
return db.getKeycards(nil, keyUID, "")
}
func (db *Database) GetKeycardByKeycardUID(keycardUID string) (*Keycard, error) {
return db.getKeycardByKeycardUID(nil, keycardUID)
}
func (db *Database) saveOrUpdateKeycardAccounts(tx *sql.Tx, kcUID string, accountsAddresses []types.Address) (err error) {
if tx == nil {
return errKeycardDbTransactionIsNil
}
for i := range accountsAddresses {
addr := accountsAddresses[i]
_, err = tx.Exec(`
INSERT OR IGNORE INTO
keycards_accounts
(
keycard_uid,
account_address
)
VALUES
(?, ?);
`, kcUID, addr)
if err != nil {
return err
}
}
return nil
}
func (db *Database) deleteKeycard(tx *sql.Tx, kcUID string) (err error) {
if tx == nil {
return errKeycardDbTransactionIsNil
}
delete, err := tx.Prepare(`
DELETE
FROM
keycards
WHERE
keycard_uid = ?
`)
if err != nil {
return err
}
defer delete.Close()
_, err = delete.Exec(kcUID)
return err
}
func (db *Database) deleteAllKeycardsWithKeyUID(tx *sql.Tx, keyUID string) (err error) {
if tx == nil {
return errKeycardDbTransactionIsNil
}
delete, err := tx.Prepare(`
DELETE
FROM
keycards
WHERE
key_uid = ?
`)
if err != nil {
return err
}
defer delete.Close()
_, err = delete.Exec(keyUID)
return err
}
func (db *Database) deleteKeycardAccounts(tx *sql.Tx, kcUID string, accountAddresses []types.Address) (err error) {
if tx == nil {
return errKeycardDbTransactionIsNil
}
inVector := strings.Repeat(",?", len(accountAddresses)-1)
query := `
DELETE
FROM
keycards_accounts
WHERE
keycard_uid = ?
AND
account_address IN (?` + inVector + `)`
delete, err := tx.Prepare(query)
if err != nil {
return err
}
defer delete.Close()
args := make([]interface{}, len(accountAddresses)+1)
args[0] = kcUID
for i, addr := range accountAddresses {
args[i+1] = addr
}
_, err = delete.Exec(args...)
return err
}
func (db *Database) SaveOrUpdateKeycard(keycard Keycard, clock uint64, updateKeypairClock bool) error {
tx, err := db.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
relatedKeypairExists, err := db.keypairExists(tx, keycard.KeyUID)
if err != nil {
return err
}
if !relatedKeypairExists {
return errCannotAddKeycardForUnknownKeypair
}
_, err = tx.Exec(`
INSERT OR IGNORE INTO
keycards
(
keycard_uid,
keycard_name,
key_uid
)
VALUES
(?, ?, ?);
UPDATE
keycards
SET
keycard_name = ?,
keycard_locked = ?,
position = ?
WHERE
keycard_uid = ?;
`, keycard.KeycardUID, keycard.KeycardName, keycard.KeyUID,
keycard.KeycardName, keycard.KeycardLocked, keycard.Position, keycard.KeycardUID)
if err != nil {
return err
}
err = db.saveOrUpdateKeycardAccounts(tx, keycard.KeycardUID, keycard.AccountsAddresses)
if err != nil {
return err
}
if updateKeypairClock {
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
}
return nil
}
func (db *Database) execKeycardUpdateQuery(kcUID string, clock uint64, field string, value interface{}) (err error) {
tx, err := db.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
keycard, err := db.getKeycardByKeycardUID(tx, kcUID)
if err != nil {
return err
}
sql := fmt.Sprintf(`UPDATE keycards SET %s = ? WHERE keycard_uid = ?`, field) // nolint: gosec
_, err = tx.Exec(sql, value, kcUID)
if err != nil {
return err
}
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
}
func (db *Database) KeycardLocked(kcUID string, clock uint64) (err error) {
return db.execKeycardUpdateQuery(kcUID, clock, "keycard_locked", true)
}
func (db *Database) KeycardUnlocked(kcUID string, clock uint64) (err error) {
return db.execKeycardUpdateQuery(kcUID, clock, "keycard_locked", false)
}
func (db *Database) UpdateKeycardUID(oldKcUID string, newKcUID string, clock uint64) (err error) {
return db.execKeycardUpdateQuery(oldKcUID, clock, "keycard_uid", newKcUID)
}
func (db *Database) SetKeycardName(kcUID string, kpName string, clock uint64) (err error) {
return db.execKeycardUpdateQuery(kcUID, clock, "keycard_name", kpName)
}
func (db *Database) DeleteKeycardAccounts(kcUID string, accountAddresses []types.Address, clock uint64) (err error) {
tx, err := db.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
keycard, err := db.getKeycardByKeycardUID(tx, kcUID)
if err != nil {
return err
}
err = db.deleteKeycardAccounts(tx, kcUID, accountAddresses)
if err != nil {
return err
}
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
}
func (db *Database) DeleteKeycard(kcUID string, clock uint64) (err error) {
tx, err := db.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
keycard, err := db.getKeycardByKeycardUID(tx, kcUID)
if err != nil {
return err
}
err = db.deleteKeycard(tx, kcUID)
if err != nil {
return err
}
return db.updateKeypairClock(tx, keycard.KeyUID, clock)
}
func (db *Database) DeleteAllKeycardsWithKeyUID(keyUID string, clock uint64) (err error) {
tx, err := db.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
err = db.deleteAllKeycardsWithKeyUID(tx, keyUID)
if err != nil {
return err
}
return db.updateKeypairClock(tx, keyUID, clock)
}
func (db *Database) GetPositionForNextNewKeycard() (uint64, error) {
var pos sql.NullInt64
err := db.db.QueryRow("SELECT MAX(position) FROM keycards").Scan(&pos)
if err != nil {
return 0, err
}
if pos.Valid {
return uint64(pos.Int64) + 1, nil
}
return 0, nil
}

View File

@@ -0,0 +1,434 @@
package accounts
import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/common"
)
func GetWatchOnlyAccountsForTest() []*Account {
wo1 := &Account{
Address: types.Address{0x11},
Type: AccountTypeWatch,
Name: "WatchOnlyAcc1",
ColorID: common.CustomizationColorPrimary,
Emoji: "emoji-1",
}
wo2 := &Account{
Address: types.Address{0x12},
Type: AccountTypeWatch,
Name: "WatchOnlyAcc2",
ColorID: common.CustomizationColorPrimary,
Emoji: "emoji-1",
}
wo3 := &Account{
Address: types.Address{0x13},
Type: AccountTypeWatch,
Name: "WatchOnlyAcc3",
ColorID: common.CustomizationColorPrimary,
Emoji: "emoji-1",
}
return []*Account{wo1, wo2, wo3}
}
func GetProfileKeypairForTest(includeChatAccount bool, includeDefaultWalletAccount bool, includeAdditionalAccounts bool) *Keypair {
kp := &Keypair{
KeyUID: "0000000000000000000000000000000000000000000000000000000000000001",
Name: "Profile Name",
Type: KeypairTypeProfile,
DerivedFrom: "0x0001",
}
if includeChatAccount {
profileAccount := &Account{
Address: types.Address{0x01},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: true,
Type: AccountTypeGenerated,
Path: "m/43'/60'/1581'/0'/0",
PublicKey: types.Hex2Bytes("0x000000001"),
Name: "Profile Name",
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
kp.Accounts = append(kp.Accounts, profileAccount)
}
if includeDefaultWalletAccount {
defaultWalletAccount := &Account{
Address: types.Address{0x02},
KeyUID: kp.KeyUID,
Wallet: true,
Chat: false,
Type: AccountTypeGenerated,
Path: "m/44'/60'/0'/0/0",
PublicKey: types.Hex2Bytes("0x000000002"),
Name: "Generated Acc 1",
Emoji: "emoji-1",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
kp.Accounts = append(kp.Accounts, defaultWalletAccount)
kp.LastUsedDerivationIndex = 0
}
if includeAdditionalAccounts {
generatedWalletAccount1 := &Account{
Address: types.Address{0x03},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeGenerated,
Path: "m/44'/60'/0'/0/1",
PublicKey: types.Hex2Bytes("0x000000003"),
Name: "Generated Acc 2",
Emoji: "emoji-2",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
kp.Accounts = append(kp.Accounts, generatedWalletAccount1)
kp.LastUsedDerivationIndex = 1
generatedWalletAccount2 := &Account{
Address: types.Address{0x04},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeGenerated,
Path: "m/44'/60'/0'/0/2",
PublicKey: types.Hex2Bytes("0x000000004"),
Name: "Generated Acc 3",
Emoji: "emoji-3",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
ProdPreferredChainIDs: "1",
TestPreferredChainIDs: "5",
}
kp.Accounts = append(kp.Accounts, generatedWalletAccount2)
kp.LastUsedDerivationIndex = 2
}
return kp
}
func GetSeedImportedKeypair1ForTest() *Keypair {
kp := &Keypair{
KeyUID: "0000000000000000000000000000000000000000000000000000000000000002",
Name: "Seed Imported 1",
Type: KeypairTypeSeed,
DerivedFrom: "0x0002",
}
seedGeneratedWalletAccount1 := &Account{
Address: types.Address{0x21},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeSeed,
Path: "m/44'/60'/0'/0/0",
PublicKey: types.Hex2Bytes("0x000000021"),
Name: "Seed Impo 1 Acc 1",
Emoji: "emoji-1",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
}
kp.Accounts = append(kp.Accounts, seedGeneratedWalletAccount1)
kp.LastUsedDerivationIndex = 0
seedGeneratedWalletAccount2 := &Account{
Address: types.Address{0x22},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeSeed,
Path: "m/44'/60'/0'/0/1",
PublicKey: types.Hex2Bytes("0x000000022"),
Name: "Seed Impo 1 Acc 2",
Emoji: "emoji-2",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
}
kp.Accounts = append(kp.Accounts, seedGeneratedWalletAccount2)
kp.LastUsedDerivationIndex = 1
return kp
}
func GetSeedImportedKeypair2ForTest() *Keypair {
kp := &Keypair{
KeyUID: "0000000000000000000000000000000000000000000000000000000000000003",
Name: "Seed Imported 2",
Type: KeypairTypeSeed,
DerivedFrom: "0x0003",
}
seedGeneratedWalletAccount1 := &Account{
Address: types.Address{0x31},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeSeed,
Path: "m/44'/60'/0'/0/0",
PublicKey: types.Hex2Bytes("0x000000031"),
Name: "Seed Impo 2 Acc 1",
Emoji: "emoji-1",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
}
kp.Accounts = append(kp.Accounts, seedGeneratedWalletAccount1)
kp.LastUsedDerivationIndex = 0
seedGeneratedWalletAccount2 := &Account{
Address: types.Address{0x32},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeSeed,
Path: "m/44'/60'/0'/0/1",
PublicKey: types.Hex2Bytes("0x000000032"),
Name: "Seed Impo 2 Acc 2",
Emoji: "emoji-2",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
}
kp.Accounts = append(kp.Accounts, seedGeneratedWalletAccount2)
kp.LastUsedDerivationIndex = 1
return kp
}
func GetPrivKeyImportedKeypairForTest() *Keypair {
kp := &Keypair{
KeyUID: "0000000000000000000000000000000000000000000000000000000000000004",
Name: "Priv Key Imported",
Type: KeypairTypeKey,
DerivedFrom: "", // no derived from for private key imported kp
}
privKeyWalletAccount := &Account{
Address: types.Address{0x41},
KeyUID: kp.KeyUID,
Wallet: false,
Chat: false,
Type: AccountTypeKey,
Path: "m",
PublicKey: types.Hex2Bytes("0x000000041"),
Name: "Priv Key Impo Acc",
Emoji: "emoji-1",
ColorID: common.CustomizationColorPrimary,
Hidden: false,
Clock: 0,
Removed: false,
Operable: AccountFullyOperable,
}
kp.Accounts = append(kp.Accounts, privKeyWalletAccount)
return kp
}
func GetProfileKeycardForTest() *Keycard {
profileKp := GetProfileKeypairForTest(true, true, true)
keycard1Addresses := []types.Address{}
for _, acc := range profileKp.Accounts {
keycard1Addresses = append(keycard1Addresses, acc.Address)
}
return &Keycard{
KeycardUID: "00000000000000000000000000000001",
KeycardName: "Card01",
KeycardLocked: false,
AccountsAddresses: keycard1Addresses,
KeyUID: profileKp.KeyUID,
Position: 0,
}
}
func GetKeycardForSeedImportedKeypair1ForTest() *Keycard {
seed1Kp := GetSeedImportedKeypair1ForTest()
keycard2Addresses := []types.Address{}
for _, acc := range seed1Kp.Accounts {
keycard2Addresses = append(keycard2Addresses, acc.Address)
}
return &Keycard{
KeycardUID: "00000000000000000000000000000002",
KeycardName: "Card02",
KeycardLocked: false,
AccountsAddresses: keycard2Addresses,
KeyUID: seed1Kp.KeyUID,
Position: 1,
}
}
func GetKeycardForSeedImportedKeypair2ForTest() *Keycard {
seed2Kp := GetSeedImportedKeypair2ForTest()
keycard4Addresses := []types.Address{}
for _, acc := range seed2Kp.Accounts {
keycard4Addresses = append(keycard4Addresses, acc.Address)
}
return &Keycard{
KeycardUID: "00000000000000000000000000000003",
KeycardName: "Card03",
KeycardLocked: false,
AccountsAddresses: keycard4Addresses,
KeyUID: seed2Kp.KeyUID,
Position: 2,
}
}
func Contains[T comparable](container []T, element T, isEqual func(T, T) bool) bool {
for _, e := range container {
if isEqual(e, element) {
return true
}
}
return false
}
func HaveSameElements[T comparable](a []T, b []T, isEqual func(T, T) bool) bool {
for _, v := range a {
if !Contains(b, v, isEqual) {
return false
}
}
return true
}
func SameAccounts(expected, real *Account) bool {
return expected.Address == real.Address &&
expected.KeyUID == real.KeyUID &&
expected.Wallet == real.Wallet &&
expected.Chat == real.Chat &&
expected.Type == real.Type &&
expected.Path == real.Path &&
string(expected.PublicKey) == string(real.PublicKey) &&
expected.Name == real.Name &&
expected.Emoji == real.Emoji &&
expected.ColorID == real.ColorID &&
expected.Hidden == real.Hidden &&
expected.Clock == real.Clock &&
expected.Removed == real.Removed &&
expected.ProdPreferredChainIDs == real.ProdPreferredChainIDs &&
expected.TestPreferredChainIDs == real.TestPreferredChainIDs
}
func SameAccountsIncludingPosition(expected, real *Account) bool {
return SameAccounts(expected, real) && expected.Position == real.Position
}
func SameAccountsWithDifferentOperable(expected, real *Account, expectedOperableValue AccountOperable) bool {
return SameAccounts(expected, real) && real.Operable == expectedOperableValue
}
func SameKeypairs(expected, real *Keypair) bool {
same := expected.KeyUID == real.KeyUID &&
expected.Name == real.Name &&
expected.Type == real.Type &&
expected.DerivedFrom == real.DerivedFrom &&
expected.LastUsedDerivationIndex == real.LastUsedDerivationIndex &&
expected.Clock == real.Clock &&
len(expected.Accounts) == len(real.Accounts)
if same {
for i := range expected.Accounts {
found := false
for j := range real.Accounts {
if SameAccounts(expected.Accounts[i], real.Accounts[j]) {
found = true
break
}
}
if !found {
return false
}
}
}
return same
}
func SameKeypairsWithDifferentSyncedFrom(expected, real *Keypair, ignoreSyncedFrom bool, expectedSyncedFromValue string,
expectedOperableValue AccountOperable) bool {
same := expected.KeyUID == real.KeyUID &&
expected.Name == real.Name &&
expected.Type == real.Type &&
expected.DerivedFrom == real.DerivedFrom &&
expected.LastUsedDerivationIndex == real.LastUsedDerivationIndex &&
expected.Clock == real.Clock &&
len(expected.Accounts) == len(real.Accounts)
if same && !ignoreSyncedFrom {
same = same && real.SyncedFrom == expectedSyncedFromValue
}
if same {
for i := range expected.Accounts {
found := false
for j := range real.Accounts {
if SameAccountsWithDifferentOperable(expected.Accounts[i], real.Accounts[j], expectedOperableValue) {
found = true
break
}
}
if !found {
return false
}
}
}
return same
}
func SameKeycards(expected, real *Keycard) bool {
same := expected.KeycardUID == real.KeycardUID &&
expected.KeyUID == real.KeyUID &&
expected.KeycardName == real.KeycardName &&
expected.KeycardLocked == real.KeycardLocked &&
expected.Position == real.Position &&
len(expected.AccountsAddresses) == len(real.AccountsAddresses)
if same {
for i := range expected.AccountsAddresses {
found := false
for j := range real.AccountsAddresses {
if expected.AccountsAddresses[i] == real.AccountsAddresses[j] {
found = true
break
}
}
if !found {
return false
}
}
}
return same
}

View File

@@ -0,0 +1,24 @@
package common
type CustomizationColor string
const (
CustomizationColorPrimary CustomizationColor = "primary"
CustomizationColorPurple CustomizationColor = "purple"
CustomizationColorIndigo CustomizationColor = "indigo"
CustomizationColorTurquoise CustomizationColor = "turquoise"
CustomizationColorBlue CustomizationColor = "blue"
CustomizationColorGreen CustomizationColor = "green"
CustomizationColorYellow CustomizationColor = "yellow"
CustomizationColorOrange CustomizationColor = "orange"
CustomizationColorRed CustomizationColor = "red"
CustomizationColorFlamingo CustomizationColor = "flamingo"
CustomizationColorBrown CustomizationColor = "brown"
CustomizationColorSky CustomizationColor = "sky"
CustomizationColorArmy CustomizationColor = "army"
CustomizationColorMagenta CustomizationColor = "magenta"
CustomizationColorCopper CustomizationColor = "copper"
CustomizationColorCamel CustomizationColor = "camel"
CustomizationColorYinYang CustomizationColor = "yinyang"
CustomizationColorBeige CustomizationColor = "beige"
)

View File

@@ -0,0 +1,480 @@
package multiaccounts
import (
"context"
"database/sql"
"encoding/json"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/common/dbsetup"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts/common"
"github.com/status-im/status-go/multiaccounts/migrations"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/sqlite"
)
type ColorHash [][2]int
// Account stores public information about account.
type Account struct {
Name string `json:"name"`
Timestamp int64 `json:"timestamp"`
Identicon string `json:"identicon"`
ColorHash ColorHash `json:"colorHash"`
ColorID int64 `json:"colorId"`
CustomizationColor common.CustomizationColor `json:"customizationColor,omitempty"`
KeycardPairing string `json:"keycard-pairing"`
KeyUID string `json:"key-uid"`
Images []images.IdentityImage `json:"images"`
KDFIterations int `json:"kdfIterations,omitempty"`
CustomizationColorClock uint64 `json:"-"`
}
func (a *Account) ToProtobuf() *protobuf.MultiAccount {
var colorHashes []*protobuf.MultiAccount_ColorHash
for _, index := range a.ColorHash {
var i []int64
for _, is := range index {
i = append(i, int64(is))
}
colorHashes = append(colorHashes, &protobuf.MultiAccount_ColorHash{Index: i})
}
var identityImages []*protobuf.MultiAccount_IdentityImage
for _, ii := range a.Images {
identityImages = append(identityImages, ii.ToProtobuf())
}
return &protobuf.MultiAccount{
Name: a.Name,
Timestamp: a.Timestamp,
Identicon: a.Identicon,
ColorHash: colorHashes,
ColorId: a.ColorID,
CustomizationColor: string(a.CustomizationColor),
KeycardPairing: a.KeycardPairing,
KeyUid: a.KeyUID,
Images: identityImages,
CustomizationColorClock: a.CustomizationColorClock,
}
}
func (a *Account) FromProtobuf(ma *protobuf.MultiAccount) {
var colorHash ColorHash
for _, index := range ma.ColorHash {
var i [2]int
for n, is := range index.Index {
i[n] = int(is)
}
colorHash = append(colorHash, i)
}
var identityImages []images.IdentityImage
for _, ii := range ma.Images {
iii := images.IdentityImage{}
iii.FromProtobuf(ii)
identityImages = append(identityImages, iii)
}
a.Name = ma.Name
a.Timestamp = ma.Timestamp
a.Identicon = ma.Identicon
a.ColorHash = colorHash
a.ColorID = ma.ColorId
a.KeycardPairing = ma.KeycardPairing
a.CustomizationColor = common.CustomizationColor(ma.CustomizationColor)
a.KeyUID = ma.KeyUid
a.Images = identityImages
a.CustomizationColorClock = ma.CustomizationColorClock
}
type MultiAccountMarshaller interface {
ToMultiAccount() *Account
}
type IdentityImageSubscriptionChange struct {
PublishExpected bool
}
type Database struct {
db *sql.DB
identityImageSubscriptions []chan *IdentityImageSubscriptionChange
}
// InitializeDB creates db file at a given path and applies migrations.
func InitializeDB(path string) (*Database, error) {
db, err := sqlite.OpenUnecryptedDB(path)
if err != nil {
return nil, err
}
err = migrations.Migrate(db, nil)
if err != nil {
return nil, err
}
return &Database{db: db}, nil
}
func (db *Database) Close() error {
return db.db.Close()
}
func (db *Database) GetAccountKDFIterationsNumber(keyUID string) (kdfIterationsNumber int, err error) {
err = db.db.QueryRow("SELECT kdfIterations FROM accounts WHERE keyUid = ?", keyUID).Scan(&kdfIterationsNumber)
if err != nil {
return -1, err
}
return
}
func (db *Database) GetAccounts() (rst []Account, err error) {
rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.customizationColor, a.customizationColorClock, a.keycardPairing, a.keyUid, a.kdfIterations, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid ORDER BY loginTimestamp DESC")
if err != nil {
return nil, err
}
defer func() {
errClose := rows.Close()
err = valueOr(err, errClose)
}()
for rows.Next() {
acc := Account{}
accLoginTimestamp := sql.NullInt64{}
accIdenticon := sql.NullString{}
accColorHash := sql.NullString{}
accColorID := sql.NullInt64{}
ii := &images.IdentityImage{}
iiName := sql.NullString{}
iiWidth := sql.NullInt64{}
iiHeight := sql.NullInt64{}
iiFileSize := sql.NullInt64{}
iiResizeTarget := sql.NullInt64{}
iiClock := sql.NullInt64{}
err = rows.Scan(
&acc.Name,
&accLoginTimestamp,
&accIdenticon,
&accColorHash,
&accColorID,
&acc.CustomizationColor,
&acc.CustomizationColorClock,
&acc.KeycardPairing,
&acc.KeyUID,
&acc.KDFIterations,
&iiName,
&ii.Payload,
&iiWidth,
&iiHeight,
&iiFileSize,
&iiResizeTarget,
&iiClock,
)
if err != nil {
return nil, err
}
acc.Timestamp = accLoginTimestamp.Int64
acc.Identicon = accIdenticon.String
acc.ColorID = accColorID.Int64
if len(accColorHash.String) != 0 {
err = json.Unmarshal([]byte(accColorHash.String), &acc.ColorHash)
if err != nil {
return nil, err
}
}
ii.KeyUID = acc.KeyUID
ii.Name = iiName.String
ii.Width = int(iiWidth.Int64)
ii.Height = int(iiHeight.Int64)
ii.FileSize = int(iiFileSize.Int64)
ii.ResizeTarget = int(iiResizeTarget.Int64)
ii.Clock = uint64(iiClock.Int64)
if ii.Name == "" && len(ii.Payload) == 0 && ii.Width == 0 && ii.Height == 0 && ii.FileSize == 0 && ii.ResizeTarget == 0 {
ii = nil
}
// Last index
li := len(rst) - 1
// Don't process nil identity images
if ii != nil {
// attach the identity image to a previously created account if present, check keyUID matches
if len(rst) > 0 && rst[li].KeyUID == acc.KeyUID {
rst[li].Images = append(rst[li].Images, *ii)
// else attach the identity image to the newly created account
} else {
acc.Images = append(acc.Images, *ii)
}
}
// Append newly created account only if this is the first loop or the keyUID doesn't match
if len(rst) == 0 || rst[li].KeyUID != acc.KeyUID {
rst = append(rst, acc)
}
}
return rst, nil
}
func (db *Database) GetAccount(keyUID string) (*Account, error) {
rows, err := db.db.Query("SELECT a.name, a.loginTimestamp, a.identicon, a.colorHash, a.colorId, a.customizationColor, a.customizationColorClock, a.keycardPairing, a.keyUid, a.kdfIterations, ii.key_uid, ii.name, ii.image_payload, ii.width, ii.height, ii.file_size, ii.resize_target, ii.clock FROM accounts AS a LEFT JOIN identity_images AS ii ON ii.key_uid = a.keyUid WHERE a.keyUid = ? ORDER BY loginTimestamp DESC", keyUID)
if err != nil {
return nil, err
}
defer func() {
errClose := rows.Close()
err = valueOr(err, errClose)
}()
acc := new(Account)
for rows.Next() {
accLoginTimestamp := sql.NullInt64{}
accIdenticon := sql.NullString{}
accColorHash := sql.NullString{}
accColorID := sql.NullInt64{}
ii := &images.IdentityImage{}
iiKeyUID := sql.NullString{}
iiName := sql.NullString{}
iiWidth := sql.NullInt64{}
iiHeight := sql.NullInt64{}
iiFileSize := sql.NullInt64{}
iiResizeTarget := sql.NullInt64{}
iiClock := sql.NullInt64{}
err = rows.Scan(
&acc.Name,
&accLoginTimestamp,
&accIdenticon,
&accColorHash,
&accColorID,
&acc.CustomizationColor,
&acc.CustomizationColorClock,
&acc.KeycardPairing,
&acc.KeyUID,
&acc.KDFIterations,
&iiKeyUID,
&iiName,
&ii.Payload,
&iiWidth,
&iiHeight,
&iiFileSize,
&iiResizeTarget,
&iiClock,
)
if err != nil {
return nil, err
}
acc.Timestamp = accLoginTimestamp.Int64
acc.Identicon = accIdenticon.String
acc.ColorID = accColorID.Int64
if len(accColorHash.String) != 0 {
err = json.Unmarshal([]byte(accColorHash.String), &acc.ColorHash)
if err != nil {
return nil, err
}
}
ii.KeyUID = iiKeyUID.String
ii.Name = iiName.String
ii.Width = int(iiWidth.Int64)
ii.Height = int(iiHeight.Int64)
ii.FileSize = int(iiFileSize.Int64)
ii.ResizeTarget = int(iiResizeTarget.Int64)
ii.Clock = uint64(iiClock.Int64)
// Don't process empty identity images
if !ii.IsEmpty() {
acc.Images = append(acc.Images, *ii)
}
}
return acc, nil
}
func (db *Database) SaveAccount(account Account) error {
colorHash, err := json.Marshal(account.ColorHash)
if err != nil {
return err
}
if account.KDFIterations <= 0 {
account.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}
_, err = db.db.Exec("INSERT OR REPLACE INTO accounts (name, identicon, colorHash, colorId, customizationColor, customizationColorClock, keycardPairing, keyUid, kdfIterations) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", account.Name, account.Identicon, colorHash, account.ColorID, account.CustomizationColor, account.CustomizationColorClock, account.KeycardPairing, account.KeyUID, account.KDFIterations)
if err != nil {
return err
}
if account.Images == nil {
return nil
}
return db.StoreIdentityImages(account.KeyUID, account.Images, false)
}
func (db *Database) UpdateDisplayName(keyUID string, displayName string) error {
_, err := db.db.Exec("UPDATE accounts SET name = ? WHERE keyUid = ?", displayName, keyUID)
return err
}
func (db *Database) UpdateAccount(account Account) error {
colorHash, err := json.Marshal(account.ColorHash)
if err != nil {
return err
}
if account.KDFIterations <= 0 {
account.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}
_, err = db.db.Exec("UPDATE accounts SET name = ?, identicon = ?, colorHash = ?, colorId = ?, customizationColor = ?, customizationColorClock = ?, keycardPairing = ?, kdfIterations = ? WHERE keyUid = ?", account.Name, account.Identicon, colorHash, account.ColorID, account.CustomizationColor, account.CustomizationColorClock, account.KeycardPairing, account.KDFIterations, account.KeyUID)
return err
}
func (db *Database) UpdateAccountKeycardPairing(keyUID string, keycardPairing string) error {
_, err := db.db.Exec("UPDATE accounts SET keycardPairing = ? WHERE keyUid = ?", keycardPairing, keyUID)
return err
}
func (db *Database) UpdateAccountTimestamp(keyUID string, loginTimestamp int64) error {
_, err := db.db.Exec("UPDATE accounts SET loginTimestamp = ? WHERE keyUid = ?", loginTimestamp, keyUID)
return err
}
func (db *Database) UpdateAccountCustomizationColor(keyUID string, color string, clock uint64) (int64, error) {
result, err := db.db.Exec("UPDATE accounts SET customizationColor = ?, customizationColorClock = ? WHERE keyUid = ? AND customizationColorClock < ?", color, clock, keyUID, clock)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
func (db *Database) DeleteAccount(keyUID string) error {
_, err := db.db.Exec("DELETE FROM accounts WHERE keyUid = ?", keyUID)
return err
}
// Account images
func (db *Database) GetIdentityImages(keyUID string) (iis []*images.IdentityImage, err error) {
rows, err := db.db.Query(`SELECT key_uid, name, image_payload, width, height, file_size, resize_target, clock FROM identity_images WHERE key_uid = ?`, keyUID)
if err != nil {
return nil, err
}
defer func() {
errClose := rows.Close()
err = valueOr(err, errClose)
}()
for rows.Next() {
ii := &images.IdentityImage{}
err = rows.Scan(&ii.KeyUID, &ii.Name, &ii.Payload, &ii.Width, &ii.Height, &ii.FileSize, &ii.ResizeTarget, &ii.Clock)
if err != nil {
return nil, err
}
iis = append(iis, ii)
}
return iis, nil
}
func (db *Database) GetIdentityImage(keyUID, it string) (*images.IdentityImage, error) {
var ii images.IdentityImage
err := db.db.QueryRow("SELECT key_uid, name, image_payload, width, height, file_size, resize_target, clock FROM identity_images WHERE key_uid = ? AND name = ?", keyUID, it).Scan(&ii.KeyUID, &ii.Name, &ii.Payload, &ii.Width, &ii.Height, &ii.FileSize, &ii.ResizeTarget, &ii.Clock)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &ii, nil
}
func (db *Database) StoreIdentityImages(keyUID string, iis []images.IdentityImage, publish bool) (err error) {
// Because SQL INSERTs are triggered in a loop use a tx to ensure a single call to the DB.
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
errRollback := tx.Rollback()
err = valueOr(err, errRollback)
}()
for i, ii := range iis {
if ii.IsEmpty() {
continue
}
iis[i].KeyUID = keyUID
_, err := tx.Exec(
"INSERT INTO identity_images (key_uid, name, image_payload, width, height, file_size, resize_target, clock) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
keyUID,
ii.Name,
ii.Payload,
ii.Width,
ii.Height,
ii.FileSize,
ii.ResizeTarget,
ii.Clock,
)
if err != nil {
return err
}
}
db.publishOnIdentityImageSubscriptions(&IdentityImageSubscriptionChange{
PublishExpected: publish,
})
return nil
}
func (db *Database) SubscribeToIdentityImageChanges() chan *IdentityImageSubscriptionChange {
s := make(chan *IdentityImageSubscriptionChange, 100)
db.identityImageSubscriptions = append(db.identityImageSubscriptions, s)
return s
}
func (db *Database) publishOnIdentityImageSubscriptions(change *IdentityImageSubscriptionChange) {
// Publish on channels, drop if buffer is full
for _, s := range db.identityImageSubscriptions {
select {
case s <- change:
default:
log.Warn("subscription channel full, dropping message")
}
}
}
func (db *Database) DeleteIdentityImage(keyUID string) error {
_, err := db.db.Exec(`DELETE FROM identity_images WHERE key_uid = ?`, keyUID)
if err != nil {
return err
}
db.publishOnIdentityImageSubscriptions(&IdentityImageSubscriptionChange{
PublishExpected: true,
})
return err
}
func valueOr(value error, or error) error {
if value != nil {
return value
}
return or
}

View File

@@ -0,0 +1,19 @@
package errors
import (
"github.com/pkg/errors"
)
var (
// ErrWalletNotUnique returned if another account has `wallet` field set to true.
ErrWalletNotUnique = errors.New("another account is set to be default wallet. disable it before using new")
// ErrChatNotUnique returned if another account has `chat` field set to true.
ErrChatNotUnique = errors.New("another account is set to be default chat. disable it before using new")
// ErrInvalidConfig returned if config isn't allowed
ErrInvalidConfig = errors.New("configuration value not allowed")
// ErrNewClockOlderThanCurrent returned if a given clock is older than the current clock
ErrNewClockOlderThanCurrent = errors.New("the new clock value is older than the current clock value")
// ErrUnrecognisedSyncSettingProtobufType returned if there is no handler or record of a given protobuf.SyncSetting_Type
ErrUnrecognisedSyncSettingProtobufType = errors.New("unrecognised protobuf.SyncSetting_Type")
ErrDbTransactionIsNil = errors.New("database transaction is nil")
)

View File

@@ -0,0 +1,551 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// 0001_accounts.down.sql (21B)
// 0001_accounts.up.sql (163B)
// 1605007189_identity_images.down.sql (29B)
// 1605007189_identity_images.up.sql (268B)
// 1606224181_drop_photo_path_from_accounts.down.sql (892B)
// 1606224181_drop_photo_path_from_accounts.up.sql (866B)
// 1648646095_image_clock.down.sql (939B)
// 1648646095_image_clock.up.sql (69B)
// 1649317600_add_color_hash.up.sql (201B)
// 1660238799_accounts_kdf.up.sql (115B)
// 1679505708_add_customization_color.up.sql (78B)
// 1687853321_add_customization_color_updated_at.up.sql (80B)
// doc.go (74B)
package migrations
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
digest [sha256.Size]byte
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var __0001_accountsDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\x4c\x4e\xce\x2f\xcd\x2b\x29\xb6\xe6\x02\x04\x00\x00\xff\xff\x96\x1e\x13\xa1\x15\x00\x00\x00")
func _0001_accountsDownSqlBytes() ([]byte, error) {
return bindataRead(
__0001_accountsDownSql,
"0001_accounts.down.sql",
)
}
func _0001_accountsDownSql() (*asset, error) {
bytes, err := _0001_accountsDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "0001_accounts.down.sql", size: 21, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd2, 0x61, 0x4c, 0x18, 0xfc, 0xc, 0xdf, 0x5c, 0x1f, 0x5e, 0xd3, 0xbd, 0xfa, 0x12, 0x5e, 0x8d, 0x8d, 0x8b, 0xb9, 0x5f, 0x99, 0x46, 0x63, 0xa5, 0xe3, 0xa6, 0x8a, 0x4, 0xf1, 0x73, 0x8a, 0xe9}}
return a, nil
}
var __0001_accountsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x1c\xcc\xb1\x6e\x83\x30\x14\x46\xe1\xdd\x4f\xf1\x8f\xad\xe4\x37\xe8\x64\xa8\x5b\xae\x42\x00\x99\x4b\x80\xd1\x02\x04\x56\x82\x8d\xc0\x19\xf2\xf6\x51\x58\x8f\xf4\x9d\xd4\x68\xc5\x1a\xac\x92\x5c\x83\xfe\x50\x94\x0c\xdd\x51\xcd\x35\xec\x30\x84\xa7\x8f\x07\xbe\xc4\x7d\x7a\x35\x6e\xc4\x4d\x99\x34\x53\x06\x95\xa1\xab\x32\x3d\x2e\xba\x97\xc2\xdb\x75\x02\xeb\x8e\x4f\x5b\x34\x79\x2e\xc5\x23\xcc\xce\xb3\x5b\xa7\x23\xda\x75\x43\x42\xff\xa0\x82\xa5\xd8\x96\x10\x43\x65\xe3\x72\x02\xf9\xf9\x0e\x76\x1f\x2b\xeb\x76\xe7\xe7\x33\x8a\x6f\xb4\xc4\x59\xd9\x30\x4c\xd9\xd2\xef\x8f\x78\x07\x00\x00\xff\xff\xab\xcf\xa2\xbd\xa3\x00\x00\x00")
func _0001_accountsUpSqlBytes() ([]byte, error) {
return bindataRead(
__0001_accountsUpSql,
"0001_accounts.up.sql",
)
}
func _0001_accountsUpSql() (*asset, error) {
bytes, err := _0001_accountsUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "0001_accounts.up.sql", size: 163, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf2, 0xfa, 0x99, 0x8e, 0x96, 0xb3, 0x13, 0x6c, 0x1f, 0x6, 0x27, 0xc5, 0xd2, 0xd4, 0xe0, 0xa5, 0x26, 0x82, 0xa7, 0x26, 0xf2, 0x68, 0x9d, 0xed, 0x9c, 0x3d, 0xbb, 0xdc, 0x37, 0x28, 0xbc, 0x1}}
return a, nil
}
var __1605007189_identity_imagesDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xc8\x4c\x49\xcd\x2b\xc9\x2c\xa9\x8c\xcf\xcc\x4d\x4c\x4f\x2d\xb6\xe6\xe5\x02\x04\x00\x00\xff\xff\xa1\x22\x72\x37\x1d\x00\x00\x00")
func _1605007189_identity_imagesDownSqlBytes() ([]byte, error) {
return bindataRead(
__1605007189_identity_imagesDownSql,
"1605007189_identity_images.down.sql",
)
}
func _1605007189_identity_imagesDownSql() (*asset, error) {
bytes, err := _1605007189_identity_imagesDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1605007189_identity_images.down.sql", size: 29, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2f, 0xcf, 0xa7, 0xae, 0xd5, 0x4f, 0xcd, 0x14, 0x63, 0x9, 0xbe, 0x39, 0x49, 0x18, 0x96, 0xb2, 0xa3, 0x8, 0x7d, 0x41, 0xdb, 0x50, 0x5d, 0xf5, 0x4d, 0xa2, 0xd, 0x8f, 0x57, 0x79, 0x77, 0x67}}
return a, nil
}
var __1605007189_identity_imagesUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xce\xc1\x6a\xc3\x30\x10\x04\xd0\xbb\xc1\xff\x30\xc7\x04\xf2\x07\x3d\xc9\xaa\x42\x44\x55\x29\x28\x4a\xd3\x9c\x84\x40\x5b\x7b\x69\xe2\x96\x58\xa5\xb8\x5f\x5f\xea\xfa\x60\x72\xdc\xc7\xec\x30\xd2\x2b\x11\x14\x82\x68\x8c\x82\xde\xc2\xba\x00\xf5\xaa\x0f\xe1\x00\xce\xd4\x17\x2e\x63\xe4\x6b\x6a\x69\x58\xd5\x15\x00\xbc\xd3\x18\xbf\x38\xe3\x45\x78\xb9\x13\x7e\xf3\xaf\x7d\xba\xd2\x1d\x4d\x5f\xf1\x33\x8d\x97\x8f\x94\xd1\x18\xd7\x4c\xe5\xf6\x68\xcc\x9c\xf8\xe6\x5c\x3a\x70\x5f\xe6\xbb\x23\x6e\xbb\xb2\x80\x37\xbe\x50\x1c\xf8\x87\x16\x76\xa3\x3f\x88\x25\xdd\x5a\x5a\x66\xf7\x5e\x3f\x0b\x7f\xc6\x93\x3a\x63\x35\x8f\xdc\x4c\xbb\xd6\x70\x16\xd2\xd9\xad\xd1\x32\xc0\xab\xbd\x11\x52\xd5\xd5\x1a\x27\x1d\x76\xee\x18\xe0\xdd\x49\x3f\x3e\xd4\xd5\x6f\x00\x00\x00\xff\xff\x8c\x6a\x0a\x57\x0c\x01\x00\x00")
func _1605007189_identity_imagesUpSqlBytes() ([]byte, error) {
return bindataRead(
__1605007189_identity_imagesUpSql,
"1605007189_identity_images.up.sql",
)
}
func _1605007189_identity_imagesUpSql() (*asset, error) {
bytes, err := _1605007189_identity_imagesUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1605007189_identity_images.up.sql", size: 268, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x50, 0xb6, 0xc1, 0x5c, 0x76, 0x72, 0x6b, 0x22, 0x34, 0xdc, 0x96, 0xdc, 0x2b, 0xfd, 0x2d, 0xbe, 0xcc, 0x1e, 0xd4, 0x5, 0x93, 0xd, 0xc2, 0x51, 0xf3, 0x1a, 0xef, 0x2b, 0x26, 0xa4, 0xeb, 0x65}}
return a, nil
}
var __1606224181_drop_photo_path_from_accountsDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x93\xc1\x6e\x9c\x30\x18\x84\xef\x3c\xc5\x1c\xdb\x95\x25\x1e\x60\x4f\x04\x9c\xc4\x2a\x0b\xc8\x98\x66\x73\x6a\x1c\xb0\x82\x15\xb0\x11\x6b\x54\xed\xdb\x57\x6b\x58\x76\x8b\xd4\xaa\xd7\x5e\xff\xf9\x7f\xcf\x68\x3e\x39\xdc\x21\xb6\xc3\x19\xae\x55\x90\x75\x6d\x27\xe3\x4e\x70\xf2\xbd\x53\xd0\xc6\x59\x48\x38\xd5\x0f\xf3\x84\x80\x1e\xe3\xb4\x4a\xa8\xdf\x7e\xd3\x8d\x32\x4e\xd7\xd6\xbc\xa1\xb6\xdd\xd4\x1b\x48\xd3\x80\x65\x77\x2b\x43\x6b\x9d\x2d\xa4\x6b\xd7\x95\x5d\x18\xc4\x9c\x46\x82\x42\xd0\x43\x91\xf3\x88\xbf\x42\x44\x0f\x29\x5d\xdd\x7f\xbc\xcb\xfa\x73\x1a\xbe\x04\x00\xf0\xa9\xce\x95\x6e\xf0\x3d\xe2\xf1\x73\xc4\x51\x70\x76\xb8\x5c\x7c\xa3\xaf\xc4\xeb\x46\xf6\x0a\x82\x1e\x05\xb2\x5c\x20\xab\xd2\x74\x9e\x77\xf6\x43\x1b\xa1\x7b\x75\x72\xb2\x1f\xf0\xc0\x9e\xc0\x32\x31\x6b\x6b\x28\x7f\x48\xae\x3e\xb5\x1c\x9b\x42\xea\x51\x9b\x0f\x2f\x04\x5f\xf1\xc2\xc4\x73\x5e\x09\xf0\xfc\x85\x25\xfb\x80\x65\x25\xe5\xe2\xf2\x50\xbe\x4d\x8b\x92\xa6\x34\x16\x4b\x5e\xe2\x73\x91\x4d\x0a\x82\xb5\x31\xb2\x35\x7c\xe4\xf9\x61\x7d\x73\x1f\x04\xe1\x0e\xc9\x68\x07\xdf\xa2\xed\x9a\x2d\x9a\x4b\xd1\xa3\xaa\x47\x25\x9d\xc2\x4f\xed\x5a\xc8\xae\x5b\x2a\x3e\x2d\x94\x58\xf6\xf4\x1b\xa3\x1b\x1c\xaf\xdc\xa1\xd9\x85\x41\xc2\xf3\x62\xc3\x61\xbf\x82\xf2\x63\xf6\xe8\x2b\xa6\x47\x56\x8a\xf2\x96\xe7\x3f\xc1\xf4\x6f\x7c\x56\xcb\xbf\xf3\x59\x98\xcf\x98\x84\x6e\xce\x98\x06\x82\xe6\xca\xeb\xf6\x61\xfe\xd0\xec\xf5\xfe\x57\x00\x00\x00\xff\xff\x72\x67\x20\xf9\x7c\x03\x00\x00")
func _1606224181_drop_photo_path_from_accountsDownSqlBytes() ([]byte, error) {
return bindataRead(
__1606224181_drop_photo_path_from_accountsDownSql,
"1606224181_drop_photo_path_from_accounts.down.sql",
)
}
func _1606224181_drop_photo_path_from_accountsDownSql() (*asset, error) {
bytes, err := _1606224181_drop_photo_path_from_accountsDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1606224181_drop_photo_path_from_accounts.down.sql", size: 892, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x90, 0x24, 0x17, 0x7, 0x80, 0x93, 0x6f, 0x8d, 0x5d, 0xaa, 0x8c, 0x79, 0x15, 0x5d, 0xb3, 0x19, 0xd7, 0xd8, 0x39, 0xf9, 0x3a, 0x63, 0x8f, 0x81, 0x15, 0xb6, 0xd6, 0x9a, 0x37, 0xa8, 0x8e, 0x9b}}
return a, nil
}
var __1606224181_drop_photo_path_from_accountsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x93\xc1\x8e\x9b\x30\x18\x84\xef\x3c\xc5\x1c\x5b\x64\x89\x07\xc8\x89\x05\x77\xd7\x2a\x01\xe4\x35\xdd\xdd\x53\x71\xc0\x0a\x56\xc0\x46\xc4\xa8\xca\xdb\x57\x01\x42\x54\xd4\x56\xb9\xed\xf5\x1f\xff\xff\x8c\xe6\x93\x03\x1f\x91\xed\x2f\x70\x8d\x82\xac\x2a\x3b\x1a\x77\x86\x93\x87\x56\x41\x1b\x67\x21\xe1\x54\xd7\xcf\x13\x02\xfa\x1e\x25\x45\x4c\xa7\xd7\x65\xdf\x58\x67\x73\xe9\x9a\x12\xd2\xd4\x60\xe9\xac\x95\xba\x56\xc6\xe9\xca\x9a\x12\x95\x6d\xc7\xce\xc0\x0f\xbc\x88\xd3\x50\x50\x08\xba\xcf\x33\x1e\xf2\x0f\x88\xf0\x29\xa1\xab\xe5\xcf\x83\xac\x4e\x63\xff\xc5\x03\x80\x93\xba\x14\xba\xc6\x8f\x90\x47\x2f\x21\x47\xce\xd9\xfe\xba\xf1\x9d\x7e\x90\x49\x37\xb2\x53\x10\xf4\x5d\x20\xcd\x04\xd2\x22\x49\xe6\x79\x6b\x8f\xda\x08\xdd\xa9\xb3\x93\x5d\x8f\x27\xf6\x0c\x96\x8a\x59\x5b\x43\x4d\x8b\xe4\xe6\x53\xc9\xa1\xce\xa5\x1e\xb4\x39\x4e\x82\xf7\x15\x6f\x4c\xbc\x64\x85\x00\xcf\xde\x58\xbc\xf3\x58\xfa\x4a\xb9\xb8\x1e\xca\xb6\x69\xf1\x4a\x13\x1a\x89\x25\x2f\x99\x72\x91\x4d\x0a\x82\xb5\x26\xb2\x35\xfc\xc6\xb3\xfd\x7a\x73\xe7\x79\x81\x8f\x78\xb0\xfd\xd4\xae\x6d\xeb\x2d\x8f\x6b\xc9\x83\xaa\x06\x25\x9d\xc2\x2f\xed\x1a\xc8\xb6\x5d\x2a\x3e\x2f\x68\x58\xfa\xfc\x0f\x30\x93\x72\x47\xe3\x07\x5e\xcc\xb3\x7c\x83\x61\xb7\x72\xfa\x63\xfc\xf9\x58\xf0\x10\x97\xc7\x80\xac\x9e\xff\x07\xb2\x40\x9e\xb9\x08\x5d\x5f\x30\xf6\x04\xf5\x0d\xd0\xfd\x5b\xe0\xef\x5d\xde\xf6\x7f\x07\x00\x00\xff\xff\xc4\x05\x28\x49\x62\x03\x00\x00")
func _1606224181_drop_photo_path_from_accountsUpSqlBytes() ([]byte, error) {
return bindataRead(
__1606224181_drop_photo_path_from_accountsUpSql,
"1606224181_drop_photo_path_from_accounts.up.sql",
)
}
func _1606224181_drop_photo_path_from_accountsUpSql() (*asset, error) {
bytes, err := _1606224181_drop_photo_path_from_accountsUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1606224181_drop_photo_path_from_accounts.up.sql", size: 866, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xff, 0x4c, 0x97, 0xee, 0xef, 0x82, 0xb8, 0x6c, 0x71, 0xbb, 0x50, 0x7b, 0xe6, 0xd9, 0x22, 0x31, 0x7c, 0x1a, 0xfe, 0x91, 0x28, 0xf6, 0x6, 0x36, 0xe, 0xb1, 0xf1, 0xc8, 0x25, 0xac, 0x7e, 0xd6}}
return a, nil
}
var __1648646095_image_clockDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xdc\x92\xbf\x6e\xdb\x30\x18\xc4\x77\x3e\xc5\x8d\x49\x40\x20\x0f\xe0\x49\x56\x68\x84\x28\x2d\xaa\x14\xd3\x34\x93\xc0\x48\x5f\x25\x22\xfa\x07\x89\x42\xe0\x3e\x7d\x61\xd9\xb0\x6b\xa3\xf5\xd6\xa5\xe3\x77\x3c\x92\x77\xfc\xf1\xf1\x01\xd9\x57\xe5\x03\xa1\xec\x69\x42\xd7\x07\x4c\xf3\x30\xf4\x63\x40\x39\xf6\xc3\xe0\xbb\x0a\x45\xdf\xcc\x6d\x37\x71\xd4\xd4\x15\x84\x4f\x42\x3b\x4f\x01\xc5\x48\x2e\x10\x1c\x02\xb5\x03\x82\x7b\x6f\x08\x0f\x8f\x2c\x36\x22\xb2\x02\x56\x6c\x53\x6d\x22\xf3\x06\x1b\xad\x95\x80\x2f\xa9\x0b\x3e\xec\x72\xdf\xba\x8a\xa6\xfc\xdd\x15\x1f\xf3\x70\xc7\x00\xe0\x83\x76\xf9\xec\x4b\x7c\x8b\x4c\xfc\x1c\x19\xbe\x88\x9d\x6b\xe9\x52\x59\x76\xe6\x83\xdb\x35\xbd\x2b\xb1\x56\x7a\x8d\x44\x5b\x24\x2f\x4a\x1d\x0c\x9f\xbe\x0c\x35\x7c\x17\x0e\x63\x4d\xbe\xaa\xc3\x79\xfe\xe1\x1b\xca\x27\xff\x93\xce\xd2\x48\xfb\x39\x0f\x6e\xac\xe8\x37\x67\x6a\xe4\x76\x1f\xfd\x8b\x78\xc3\xdd\x31\x1d\x5f\x12\xdd\x43\x27\x88\x75\xb2\x51\x32\xb6\x30\x22\x55\x51\x2c\xd8\x3d\x5e\xa5\x7d\xd6\x2f\x16\x46\xbf\xca\xa7\x15\x63\x32\xc9\x84\xb1\x90\x89\xd5\x7f\xa9\x8e\x4c\x28\x11\x5b\x5c\x1c\xcf\x2f\x4b\xf2\x43\x25\x7e\xac\xc2\xcf\x15\xf8\x55\xf4\x8d\xd1\xdb\xeb\x8b\x56\x8c\x3d\x19\x9d\xfe\x19\xc0\x8a\xb1\x13\xaa\x65\x5d\x6e\x96\xd7\x14\xdf\x65\x66\xb3\x6b\xf7\x7f\xcb\xe9\x16\xa8\x7f\x4f\xe8\xf8\x15\x6e\x82\x3a\x79\x7e\x05\x00\x00\xff\xff\xce\x2f\xe3\x37\xab\x03\x00\x00")
func _1648646095_image_clockDownSqlBytes() ([]byte, error) {
return bindataRead(
__1648646095_image_clockDownSql,
"1648646095_image_clock.down.sql",
)
}
func _1648646095_image_clockDownSql() (*asset, error) {
bytes, err := _1648646095_image_clockDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1648646095_image_clock.down.sql", size: 939, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x4d, 0xa8, 0x1f, 0xf, 0xe0, 0xd7, 0xc9, 0x68, 0x98, 0xd8, 0x37, 0xb8, 0xba, 0x9e, 0xb2, 0x19, 0xf3, 0xc4, 0x73, 0x80, 0x3, 0x17, 0x2a, 0x53, 0x68, 0x10, 0x13, 0x54, 0x99, 0xb1, 0xf5, 0x1c}}
return a, nil
}
var __1648646095_image_clockUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xc8\x4c\x49\xcd\x2b\xc9\x2c\xa9\x8c\xcf\xcc\x4d\x4c\x4f\x2d\x56\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x48\xce\xc9\x4f\xce\x56\xf0\xf4\x0b\x51\xf0\xf3\x0f\x51\xf0\x0b\xf5\xf1\x51\x70\x71\x75\x73\x0c\xf5\x09\x51\x30\xb0\xe6\x02\x04\x00\x00\xff\xff\x22\x35\x20\xbf\x45\x00\x00\x00")
func _1648646095_image_clockUpSqlBytes() ([]byte, error) {
return bindataRead(
__1648646095_image_clockUpSql,
"1648646095_image_clock.up.sql",
)
}
func _1648646095_image_clockUpSql() (*asset, error) {
bytes, err := _1648646095_image_clockUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1648646095_image_clock.up.sql", size: 69, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x98, 0xa6, 0xa4, 0x4e, 0x4e, 0xca, 0x17, 0x56, 0xea, 0xfb, 0xf0, 0xa9, 0x81, 0x95, 0xe, 0x80, 0x52, 0x1, 0x47, 0x9b, 0xde, 0x14, 0xfa, 0x72, 0xc9, 0x62, 0x6f, 0x24, 0xa2, 0xc, 0x32, 0x50}}
return a, nil
}
var __1649317600_add_color_hashUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\x4c\x4e\xce\x2f\xcd\x2b\x29\x56\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x48\xce\xcf\xc9\x2f\xf2\x48\x2c\xce\x50\x08\x71\x8d\x08\x51\xf0\xf3\x0f\x51\xf0\x0b\xf5\xf1\x51\x70\x71\x75\x73\x0c\xf5\x09\x51\x50\x52\xb2\xe6\x22\xca\x0c\xcf\x14\x05\x4f\x3f\x2c\x06\x18\x58\x73\x85\x06\xb8\x38\x86\x20\x69\x0d\x76\x0d\x41\xb2\xd7\x16\x6c\x07\x4e\x35\x9e\x29\x0a\xb6\x20\x43\x00\x01\x00\x00\xff\xff\xfa\xaf\xaf\xd9\xc9\x00\x00\x00")
func _1649317600_add_color_hashUpSqlBytes() ([]byte, error) {
return bindataRead(
__1649317600_add_color_hashUpSql,
"1649317600_add_color_hash.up.sql",
)
}
func _1649317600_add_color_hashUpSql() (*asset, error) {
bytes, err := _1649317600_add_color_hashUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1649317600_add_color_hash.up.sql", size: 201, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1a, 0xf, 0x37, 0x6d, 0xcf, 0x99, 0xc9, 0x2e, 0xdc, 0x70, 0x11, 0xb4, 0x36, 0x26, 0x4f, 0x39, 0xa8, 0x44, 0xf, 0xcb, 0xcc, 0x81, 0x74, 0x7a, 0x88, 0xaa, 0x54, 0x8c, 0xc4, 0xe, 0x56, 0x4f}}
return a, nil
}
var __1660238799_accounts_kdfUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\x4c\x4e\xce\x2f\xcd\x2b\x29\x56\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\x4e\x49\xf3\x2c\x49\x2d\x4a\x2c\xc9\xcc\xcf\x2b\x56\xf0\xf4\x0b\x51\xf0\xf3\x0f\x51\xf0\x0b\xf5\xf1\x51\x70\x71\x75\x73\x0c\xf5\x09\x51\x30\x36\x32\x30\xb0\xe6\x0a\x0d\x70\x71\x0c\x41\x32\x23\xd8\x35\x04\x4d\xb3\x2d\x54\x25\x20\x00\x00\xff\xff\x37\x9c\xbc\xd5\x73\x00\x00\x00")
func _1660238799_accounts_kdfUpSqlBytes() ([]byte, error) {
return bindataRead(
__1660238799_accounts_kdfUpSql,
"1660238799_accounts_kdf.up.sql",
)
}
func _1660238799_accounts_kdfUpSql() (*asset, error) {
bytes, err := _1660238799_accounts_kdfUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1660238799_accounts_kdf.up.sql", size: 115, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xdf, 0xe6, 0x7a, 0x69, 0x25, 0x42, 0x3b, 0x9c, 0x20, 0xf5, 0xcb, 0xae, 0xb0, 0xb3, 0x1b, 0x66, 0xc2, 0x5d, 0xd0, 0xc1, 0x59, 0xe8, 0xa9, 0xc5, 0x69, 0x58, 0x8f, 0xae, 0xe6, 0xd1, 0x4c, 0x53}}
return a, nil
}
var __1679505708_add_customization_colorUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\x4c\x4e\xce\x2f\xcd\x2b\x29\x56\x70\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x48\x2e\x2d\x2e\xc9\xcf\xcd\xac\x4a\x2c\xc9\xcc\xcf\x73\xce\xcf\xc9\x2f\x52\x08\x73\x0c\x72\xf6\x70\x0c\x52\x70\x71\x75\x73\x0c\xf5\x09\x51\x50\x2a\x28\xca\xcc\x4d\x2c\xaa\x54\xb2\xe6\x02\x04\x00\x00\xff\xff\x08\xb6\x89\xf4\x4e\x00\x00\x00")
func _1679505708_add_customization_colorUpSqlBytes() ([]byte, error) {
return bindataRead(
__1679505708_add_customization_colorUpSql,
"1679505708_add_customization_color.up.sql",
)
}
func _1679505708_add_customization_colorUpSql() (*asset, error) {
bytes, err := _1679505708_add_customization_colorUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1679505708_add_customization_color.up.sql", size: 78, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa9, 0xe1, 0x3d, 0xaa, 0x5d, 0x35, 0x87, 0x8a, 0x8b, 0xe9, 0x4a, 0xa6, 0x7b, 0x85, 0xbc, 0x33, 0x11, 0xc7, 0x7d, 0x61, 0xac, 0x65, 0x59, 0xda, 0x32, 0x59, 0x68, 0x9d, 0xa1, 0x10, 0x7b, 0xa9}}
return a, nil
}
var __1687853321_add_customization_color_updated_atUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x04\xc0\x31\x0a\x43\x21\x0c\x06\xe0\xbd\xa7\xf8\x8f\xd0\xbd\x53\xaa\x16\x0a\x69\x84\x12\x0f\x20\x99\xa4\xd6\xc0\x53\x97\x77\xfa\xf7\x11\x6b\xfa\x42\xe9\xc9\x09\xd5\xcc\xf7\x58\x13\x14\x23\x42\xe6\xf2\x11\xd8\x9e\xcb\xff\xed\xac\xab\xf9\x08\xde\xfd\x08\xdd\xed\x87\xb7\x28\x24\x2b\xa4\x30\x23\xa6\x17\x15\x56\xdc\x1f\xb7\x2b\x00\x00\xff\xff\xfd\x48\x7a\xa4\x50\x00\x00\x00")
func _1687853321_add_customization_color_updated_atUpSqlBytes() ([]byte, error) {
return bindataRead(
__1687853321_add_customization_color_updated_atUpSql,
"1687853321_add_customization_color_updated_at.up.sql",
)
}
func _1687853321_add_customization_color_updated_atUpSql() (*asset, error) {
bytes, err := _1687853321_add_customization_color_updated_atUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1687853321_add_customization_color_updated_at.up.sql", size: 80, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa8, 0xc2, 0x9, 0xec, 0xf4, 0xd1, 0x46, 0x29, 0xc5, 0xce, 0x4d, 0xd4, 0xf, 0x9c, 0xfa, 0x62, 0x1, 0x29, 0xe6, 0xd2, 0xd5, 0xe, 0xf0, 0x27, 0x81, 0x4a, 0x82, 0x25, 0x5f, 0x67, 0xff, 0xd1}}
return a, nil
}
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00")
func docGoBytes() ([]byte, error) {
return bindataRead(
_docGo,
"doc.go",
)
}
func docGo() (*asset, error) {
bytes, err := docGoBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1704726861, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xde, 0x7c, 0x28, 0xcd, 0x47, 0xf2, 0xfa, 0x7c, 0x51, 0x2d, 0xd8, 0x38, 0xb, 0xb0, 0x34, 0x9d, 0x4c, 0x62, 0xa, 0x9e, 0x28, 0xc3, 0x31, 0x23, 0xd9, 0xbb, 0x89, 0x9f, 0xa0, 0x89, 0x1f, 0xe8}}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetString returns the asset contents as a string (instead of a []byte).
func AssetString(name string) (string, error) {
data, err := Asset(name)
return string(data), err
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// MustAssetString is like AssetString but panics when Asset would return an
// error. It simplifies safe initialization of global variables.
func MustAssetString(name string) string {
return string(MustAsset(name))
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetDigest returns the digest of the file with the given name. It returns an
// error if the asset could not be found or the digest could not be loaded.
func AssetDigest(name string) ([sha256.Size]byte, error) {
canonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[canonicalName]; ok {
a, err := f()
if err != nil {
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
}
return a.digest, nil
}
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
}
// Digests returns a map of all known files and their checksums.
func Digests() (map[string][sha256.Size]byte, error) {
mp := make(map[string][sha256.Size]byte, len(_bindata))
for name := range _bindata {
a, err := _bindata[name]()
if err != nil {
return nil, err
}
mp[name] = a.digest
}
return mp, nil
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"0001_accounts.down.sql": _0001_accountsDownSql,
"0001_accounts.up.sql": _0001_accountsUpSql,
"1605007189_identity_images.down.sql": _1605007189_identity_imagesDownSql,
"1605007189_identity_images.up.sql": _1605007189_identity_imagesUpSql,
"1606224181_drop_photo_path_from_accounts.down.sql": _1606224181_drop_photo_path_from_accountsDownSql,
"1606224181_drop_photo_path_from_accounts.up.sql": _1606224181_drop_photo_path_from_accountsUpSql,
"1648646095_image_clock.down.sql": _1648646095_image_clockDownSql,
"1648646095_image_clock.up.sql": _1648646095_image_clockUpSql,
"1649317600_add_color_hash.up.sql": _1649317600_add_color_hashUpSql,
"1660238799_accounts_kdf.up.sql": _1660238799_accounts_kdfUpSql,
"1679505708_add_customization_color.up.sql": _1679505708_add_customization_colorUpSql,
"1687853321_add_customization_color_updated_at.up.sql": _1687853321_add_customization_color_updated_atUpSql,
"doc.go": docGo,
}
// AssetDebug is true if the assets were built with the debug flag enabled.
const AssetDebug = false
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
//
// data/
// foo.txt
// img/
// a.png
// b.png
//
// then AssetDir("data") would return []string{"foo.txt", "img"},
// AssetDir("data/img") would return []string{"a.png", "b.png"},
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
canonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(canonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"0001_accounts.down.sql": {_0001_accountsDownSql, map[string]*bintree{}},
"0001_accounts.up.sql": {_0001_accountsUpSql, map[string]*bintree{}},
"1605007189_identity_images.down.sql": {_1605007189_identity_imagesDownSql, map[string]*bintree{}},
"1605007189_identity_images.up.sql": {_1605007189_identity_imagesUpSql, map[string]*bintree{}},
"1606224181_drop_photo_path_from_accounts.down.sql": {_1606224181_drop_photo_path_from_accountsDownSql, map[string]*bintree{}},
"1606224181_drop_photo_path_from_accounts.up.sql": {_1606224181_drop_photo_path_from_accountsUpSql, map[string]*bintree{}},
"1648646095_image_clock.down.sql": {_1648646095_image_clockDownSql, map[string]*bintree{}},
"1648646095_image_clock.up.sql": {_1648646095_image_clockUpSql, map[string]*bintree{}},
"1649317600_add_color_hash.up.sql": {_1649317600_add_color_hashUpSql, map[string]*bintree{}},
"1660238799_accounts_kdf.up.sql": {_1660238799_accounts_kdfUpSql, map[string]*bintree{}},
"1679505708_add_customization_color.up.sql": {_1679505708_add_customization_colorUpSql, map[string]*bintree{}},
"1687853321_add_customization_color_updated_at.up.sql": {_1687853321_add_customization_color_updated_atUpSql, map[string]*bintree{}},
"doc.go": {docGo, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory.
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = os.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
}
// RestoreAssets restores an asset under the given directory recursively.
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
canonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
}

View File

@@ -0,0 +1,20 @@
package migrations
import (
"database/sql"
bindata "github.com/status-im/migrate/v4/source/go_bindata"
"github.com/status-im/status-go/sqlite"
)
// Migrate applies migrations.
// see Migrate in vendor/status-go/sqlite/migrate.go
func Migrate(db *sql.DB, customSteps []*sqlite.PostStep) error {
return sqlite.Migrate(db, bindata.Resource(
AssetNames(),
func(name string) ([]byte, error) {
return Asset(name)
},
), customSteps, nil)
}

View File

@@ -0,0 +1,602 @@
package settings
import (
"github.com/status-im/status-go/multiaccounts/errors"
"github.com/status-im/status-go/protocol/protobuf"
)
const (
DBColumnMnemonic = "mnemonic"
)
var (
AnonMetricsShouldSend = SettingField{
reactFieldName: "anon-metrics/should-send?",
dBColumnName: "anon_metrics_should_send",
valueHandler: BoolHandler,
}
Appearance = SettingField{
reactFieldName: "appearance",
dBColumnName: "appearance",
}
AutoMessageEnabled = SettingField{
reactFieldName: "auto-message-enabled?",
dBColumnName: "auto_message_enabled",
valueHandler: BoolHandler,
}
BackupEnabled = SettingField{
reactFieldName: "backup-enabled?",
dBColumnName: "backup_enabled",
valueHandler: BoolHandler,
}
BackupFetched = SettingField{
reactFieldName: "backup-fetched?",
dBColumnName: "backup_fetched",
valueHandler: BoolHandler,
}
ChaosMode = SettingField{
reactFieldName: "chaos-mode?",
dBColumnName: "chaos_mode",
valueHandler: BoolHandler,
}
Currency = SettingField{
reactFieldName: "currency",
dBColumnName: "currency",
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: currencyProtobufFactory,
fromStruct: currencyProtobufFactoryStruct,
valueFromProtobuf: StringFromSyncProtobuf,
protobufType: protobuf.SyncSetting_CURRENCY,
},
}
CurrentUserStatus = SettingField{
reactFieldName: "current-user-status",
dBColumnName: "current_user_status",
valueHandler: JSONBlobHandler,
}
CustomBootNodes = SettingField{
reactFieldName: "custom-bootnodes",
dBColumnName: "custom_bootnodes",
valueHandler: JSONBlobHandler,
}
CustomBootNodesEnabled = SettingField{
reactFieldName: "custom-bootnodes-enabled?",
dBColumnName: "custom_bootnodes_enabled",
valueHandler: JSONBlobHandler,
}
DappsAddress = SettingField{
reactFieldName: "dapps-address",
dBColumnName: "dapps_address",
valueHandler: AddressHandler,
}
DefaultSyncPeriod = SettingField{
reactFieldName: "default-sync-period",
dBColumnName: "default_sync_period",
}
DeviceName = SettingField{
reactFieldName: "device-name",
dBColumnName: "device_name",
}
DisplayName = SettingField{
reactFieldName: "display-name",
dBColumnName: "display_name",
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: displayNameProtobufFactory,
fromStruct: displayNameProtobufFactoryStruct,
valueFromProtobuf: StringFromSyncProtobuf,
protobufType: protobuf.SyncSetting_DISPLAY_NAME,
},
}
Bio = SettingField{
reactFieldName: "bio",
dBColumnName: "bio",
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: bioProtobufFactory,
fromStruct: bioProtobufFactoryStruct,
valueFromProtobuf: StringFromSyncProtobuf,
protobufType: protobuf.SyncSetting_BIO,
},
}
EIP1581Address = SettingField{
reactFieldName: "eip1581-address",
dBColumnName: "eip1581_address",
valueHandler: AddressHandler,
}
Fleet = SettingField{
reactFieldName: "fleet",
dBColumnName: "fleet",
}
GifAPIKey = SettingField{
reactFieldName: "gifs/api-key",
dBColumnName: "gif_api_key",
}
GifFavourites = SettingField{
reactFieldName: "gifs/favorite-gifs",
dBColumnName: "gif_favorites",
valueHandler: JSONBlobHandler,
// TODO resolve issue 8 https://github.com/status-im/status-mobile/pull/13053#issuecomment-1065179963
// The reported issue is not directly related, but I suspect that gifs suffer the same issue
syncProtobufFactory: &SyncProtobufFactory{
inactive: true, // Remove after issue is resolved
fromInterface: gifFavouritesProtobufFactory,
fromStruct: gifFavouritesProtobufFactoryStruct,
valueFromProtobuf: BytesFromSyncProtobuf,
protobufType: protobuf.SyncSetting_GIF_FAVOURITES,
},
}
GifRecents = SettingField{
reactFieldName: "gifs/recent-gifs",
dBColumnName: "gif_recents",
valueHandler: JSONBlobHandler,
// TODO resolve issue 8 https://github.com/status-im/status-mobile/pull/13053#issuecomment-1065179963
// The reported issue is not directly related, but I suspect that gifs suffer the same issue
syncProtobufFactory: &SyncProtobufFactory{
inactive: true, // Remove after issue is resolved
fromInterface: gifRecentsProtobufFactory,
fromStruct: gifRecentsProtobufFactoryStruct,
valueFromProtobuf: BytesFromSyncProtobuf,
protobufType: protobuf.SyncSetting_GIF_RECENTS,
},
}
HideHomeTooltip = SettingField{
reactFieldName: "hide-home-tooltip?",
dBColumnName: "hide_home_tooltip",
valueHandler: BoolHandler,
}
KeycardInstanceUID = SettingField{
reactFieldName: "keycard-instance_uid",
dBColumnName: "keycard_instance_uid",
}
KeycardPairedOn = SettingField{
reactFieldName: "keycard-paired_on",
dBColumnName: "keycard_paired_on",
}
KeycardPairing = SettingField{
reactFieldName: "keycard-pairing",
dBColumnName: "keycard_pairing",
}
LastBackup = SettingField{
reactFieldName: "last-backup",
dBColumnName: "last_backup",
}
LastUpdated = SettingField{
reactFieldName: "last-updated",
dBColumnName: "last_updated",
}
LatestDerivedPath = SettingField{
reactFieldName: "latest-derived-path",
dBColumnName: "latest_derived_path",
}
LinkPreviewRequestEnabled = SettingField{
reactFieldName: "link-preview-request-enabled",
dBColumnName: "link_preview_request_enabled",
valueHandler: BoolHandler,
}
LinkPreviewsEnabledSites = SettingField{
reactFieldName: "link-previews-enabled-sites",
dBColumnName: "link_previews_enabled_sites",
valueHandler: JSONBlobHandler,
}
LogLevel = SettingField{
reactFieldName: "log-level",
dBColumnName: "log_level",
}
MessagesFromContactsOnly = SettingField{
reactFieldName: "messages-from-contacts-only",
dBColumnName: "messages_from_contacts_only",
valueHandler: BoolHandler,
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: messagesFromContactsOnlyProtobufFactory,
fromStruct: messagesFromContactsOnlyProtobufFactoryStruct,
valueFromProtobuf: BoolFromSyncProtobuf,
protobufType: protobuf.SyncSetting_MESSAGES_FROM_CONTACTS_ONLY,
},
}
Mnemonic = SettingField{
reactFieldName: DBColumnMnemonic,
dBColumnName: DBColumnMnemonic,
}
MnemonicRemoved = SettingField{
reactFieldName: "mnemonic-removed?",
dBColumnName: "mnemonic_removed",
valueHandler: BoolHandler,
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: mnemonicRemovedProtobufFactory,
fromStruct: mnemonicRemovedProtobufFactoryStruct,
valueFromProtobuf: BoolFromSyncProtobuf,
protobufType: protobuf.SyncSetting_MNEMONIC_REMOVED,
},
}
MutualContactEnabled = SettingField{
reactFieldName: "mutual-contact-enabled?",
dBColumnName: "mutual_contact_enabled",
valueHandler: BoolHandler,
}
Name = SettingField{
reactFieldName: "name",
dBColumnName: "name",
}
NetworksCurrentNetwork = SettingField{
reactFieldName: "networks/current-network",
dBColumnName: "current_network",
}
NetworksNetworks = SettingField{
reactFieldName: "networks/networks",
dBColumnName: "networks",
valueHandler: JSONBlobHandler,
}
NodeConfig = SettingField{
reactFieldName: "node-config",
dBColumnName: "node_config",
valueHandler: NodeConfigHandler,
}
// NotificationsEnabled - we should remove this and realated things once mobile team starts usign `settings_notifications` package
NotificationsEnabled = SettingField{
reactFieldName: "notifications-enabled?",
dBColumnName: "notifications_enabled",
valueHandler: BoolHandler,
}
OpenseaEnabled = SettingField{
reactFieldName: "opensea-enabled?",
dBColumnName: "opensea_enabled",
valueHandler: BoolHandler,
}
PhotoPath = SettingField{
reactFieldName: "photo-path",
dBColumnName: "photo_path",
}
PinnedMailservers = SettingField{
reactFieldName: "pinned-mailservers",
dBColumnName: "pinned_mailservers",
valueHandler: JSONBlobHandler,
}
PreferredName = SettingField{
reactFieldName: "preferred-name",
dBColumnName: "preferred_name",
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: preferredNameProtobufFactory,
fromStruct: preferredNameProtobufFactoryStruct,
valueFromProtobuf: StringFromSyncProtobuf,
protobufType: protobuf.SyncSetting_PREFERRED_NAME,
},
}
PreviewPrivacy = SettingField{
reactFieldName: "preview-privacy?",
dBColumnName: "preview_privacy",
valueHandler: BoolHandler,
// TODO resolved issue 7 https://github.com/status-im/status-mobile/pull/13053#issuecomment-1065179963
syncProtobufFactory: &SyncProtobufFactory{
inactive: true, // Remove after issue is resolved
fromInterface: previewPrivacyProtobufFactory,
fromStruct: previewPrivacyProtobufFactoryStruct,
valueFromProtobuf: BoolFromSyncProtobuf,
protobufType: protobuf.SyncSetting_PREVIEW_PRIVACY,
},
}
ProfilePicturesShowTo = SettingField{
reactFieldName: "profile-pictures-show-to",
dBColumnName: "profile_pictures_show_to",
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: profilePicturesShowToProtobufFactory,
fromStruct: profilePicturesShowToProtobufFactoryStruct,
valueFromProtobuf: Int64FromSyncProtobuf,
protobufType: protobuf.SyncSetting_PROFILE_PICTURES_SHOW_TO,
},
}
ProfilePicturesVisibility = SettingField{
reactFieldName: "profile-pictures-visibility",
dBColumnName: "profile_pictures_visibility",
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: profilePicturesVisibilityProtobufFactory,
fromStruct: profilePicturesVisibilityProtobufFactoryStruct,
valueFromProtobuf: Int64FromSyncProtobuf,
protobufType: protobuf.SyncSetting_PROFILE_PICTURES_VISIBILITY,
},
}
PublicKey = SettingField{
reactFieldName: "public-key",
dBColumnName: "public_key",
}
PushNotificationsBlockMentions = SettingField{
reactFieldName: "push-notifications-block-mentions?",
dBColumnName: "push_notifications_block_mentions",
valueHandler: BoolHandler,
}
PushNotificationsFromContactsOnly = SettingField{
reactFieldName: "push-notifications-from-contacts-only?",
dBColumnName: "push_notifications_from_contacts_only",
valueHandler: BoolHandler,
}
PushNotificationsServerEnabled = SettingField{
reactFieldName: "push-notifications-server-enabled?",
dBColumnName: "push_notifications_server_enabled",
valueHandler: BoolHandler,
}
RememberSyncingChoice = SettingField{
reactFieldName: "remember-syncing-choice?",
dBColumnName: "remember_syncing_choice",
valueHandler: BoolHandler,
}
RemotePushNotificationsEnabled = SettingField{
reactFieldName: "remote-push-notifications-enabled?",
dBColumnName: "remote_push_notifications_enabled",
valueHandler: BoolHandler,
}
SendPushNotifications = SettingField{
reactFieldName: "send-push-notifications?",
dBColumnName: "send_push_notifications",
valueHandler: BoolHandler,
}
SendStatusUpdates = SettingField{
reactFieldName: "send-status-updates?",
dBColumnName: "send_status_updates",
valueHandler: BoolHandler,
// TODO resolve issue 10 https://github.com/status-im/status-mobile/pull/13053#issuecomment-1075352256
syncProtobufFactory: &SyncProtobufFactory{
inactive: true, // Remove after issue is resolved
fromInterface: sendStatusUpdatesProtobufFactory,
fromStruct: sendStatusUpdatesProtobufFactoryStruct,
valueFromProtobuf: BoolFromSyncProtobuf,
protobufType: protobuf.SyncSetting_SEND_STATUS_UPDATES,
},
}
StickersPacksInstalled = SettingField{
reactFieldName: "stickers/packs-installed",
dBColumnName: "stickers_packs_installed",
valueHandler: JSONBlobHandler,
syncProtobufFactory: &SyncProtobufFactory{
inactive: true, // TODO current version of stickers introduces a regression on deleting sticker packs
fromInterface: stickersPacksInstalledProtobufFactory,
fromStruct: stickersPacksInstalledProtobufFactoryStruct,
valueFromProtobuf: BytesFromSyncProtobuf,
protobufType: protobuf.SyncSetting_STICKERS_PACKS_INSTALLED,
},
}
StickersPacksPending = SettingField{
reactFieldName: "stickers/packs-pending",
dBColumnName: "stickers_packs_pending",
valueHandler: JSONBlobHandler,
syncProtobufFactory: &SyncProtobufFactory{
inactive: true, // TODO current version of stickers introduces a regression on deleting sticker packs
fromInterface: stickersPacksPendingProtobufFactory,
fromStruct: stickersPacksPendingProtobufFactoryStruct,
valueFromProtobuf: BytesFromSyncProtobuf,
protobufType: protobuf.SyncSetting_STICKERS_PACKS_PENDING,
},
}
StickersRecentStickers = SettingField{
reactFieldName: "stickers/recent-stickers",
dBColumnName: "stickers_recent_stickers",
valueHandler: JSONBlobHandler,
syncProtobufFactory: &SyncProtobufFactory{
inactive: true, // TODO current version of stickers introduces a regression on deleting sticker packs
fromInterface: stickersRecentStickersProtobufFactory,
fromStruct: stickersRecentStickersProtobufFactoryStruct,
valueFromProtobuf: BytesFromSyncProtobuf,
protobufType: protobuf.SyncSetting_STICKERS_RECENT_STICKERS,
},
}
SyncingOnMobileNetwork = SettingField{
reactFieldName: "syncing-on-mobile-network?",
dBColumnName: "syncing_on_mobile_network",
valueHandler: BoolHandler,
}
TelemetryServerURL = SettingField{
reactFieldName: "telemetry-server-url",
dBColumnName: "telemetry_server_url",
}
TestNetworksEnabled = SettingField{
reactFieldName: "test-networks-enabled?",
dBColumnName: "test_networks_enabled",
valueHandler: BoolHandler,
}
IsSepoliaEnabled = SettingField{
reactFieldName: "is-sepolia-enabled?",
dBColumnName: "is_sepolia_enabled",
valueHandler: BoolHandler,
}
TokenGroupByCommunity = SettingField{
reactFieldName: "token-group-by-community?",
dBColumnName: "wallet_token_preferences_group_by_community",
valueHandler: BoolHandler,
}
ShowCommunityAssetWhenSendingTokens = SettingField{
reactFieldName: "show-community-asset-when-sending-tokens?",
dBColumnName: "wallet_show_community_asset_when_sending_tokens",
valueHandler: BoolHandler,
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: showCommunityAssetWhenSendingTokensProtobufFactory,
fromStruct: showCommunityAssetWhenSendingTokensProtobufFactoryStruct,
valueFromProtobuf: BoolFromSyncProtobuf,
protobufType: protobuf.SyncSetting_SHOW_COMMUNITY_ASSET_WHEN_SENDING_TOKENS,
},
}
DisplayAssetsBelowBalance = SettingField{
reactFieldName: "display-assets-below-balance?",
dBColumnName: "wallet_display_assets_below_balance",
valueHandler: BoolHandler,
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: displayAssetsBelowBalanceProtobufFactory,
fromStruct: displayAssetsBelowBalanceProtobufFactoryStruct,
valueFromProtobuf: BoolFromSyncProtobuf,
protobufType: protobuf.SyncSetting_DISPLAY_ASSETS_BELOW_BALANCE,
},
}
DisplayAssetsBelowBalanceThreshold = SettingField{
reactFieldName: "display-assets-below-balance-threshold",
dBColumnName: "wallet_display_assets_below_balance_threshold",
valueHandler: Int64Handler,
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: displayAssetsBelowBalanceThresholdProtobufFactory,
fromStruct: displayAssetsBelowBalanceThresholdProtobufFactoryStruct,
valueFromProtobuf: Int64FromSyncProtobuf,
protobufType: protobuf.SyncSetting_DISPLAY_ASSETS_BELOW_BALANCE_THRESHOLD,
},
}
CollectibleGroupByCollection = SettingField{
reactFieldName: "collectible-group-by-collection?",
dBColumnName: "wallet_collectible_preferences_group_by_collection",
valueHandler: BoolHandler,
}
CollectibleGroupByCommunity = SettingField{
reactFieldName: "collectible-group-by-community?",
dBColumnName: "wallet_collectible_preferences_group_by_community",
valueHandler: BoolHandler,
}
UseMailservers = SettingField{
reactFieldName: "use-mailservers?",
dBColumnName: "use_mailservers",
valueHandler: BoolHandler,
}
WakuBloomFilterMode = SettingField{
reactFieldName: "waku-bloom-filter-mode",
dBColumnName: "waku_bloom_filter_mode",
valueHandler: BoolHandler,
}
WalletSetUpPassed = SettingField{
reactFieldName: "wallet-set-up-passed?",
dBColumnName: "wallet_set_up_passed",
valueHandler: BoolHandler,
}
WalletVisibleTokens = SettingField{
reactFieldName: "wallet/visible-tokens",
dBColumnName: "wallet_visible_tokens",
valueHandler: JSONBlobHandler,
}
WebviewAllowPermissionRequests = SettingField{
reactFieldName: "webview-allow-permission-requests?",
dBColumnName: "webview_allow_permission_requests",
valueHandler: BoolHandler,
}
WalletRootAddress = SettingField{
reactFieldName: "wallet-root-address",
dBColumnName: "wallet_root_address",
valueHandler: AddressHandler,
}
MasterAddress = SettingField{
reactFieldName: "address",
dBColumnName: "address",
valueHandler: AddressHandler,
}
ProfileMigrationNeeded = SettingField{
reactFieldName: "profile-migration-needed",
dBColumnName: "profile_migration_needed",
valueHandler: BoolHandler,
}
URLUnfurlingMode = SettingField{
reactFieldName: "url-unfurling-mode",
dBColumnName: "url_unfurling_mode",
syncProtobufFactory: &SyncProtobufFactory{
fromInterface: urlUnfurlingModeProtobufFactory,
fromStruct: urlUnfurlingModeProtobufFactoryStruct,
valueFromProtobuf: Int64FromSyncProtobuf,
protobufType: protobuf.SyncSetting_URL_UNFURLING_MODE,
},
}
OmitTransfersHistoryScan = SettingField{
reactFieldName: "omit-transfers-history-scan",
dBColumnName: "omit_transfers_history_scan",
valueHandler: BoolHandler,
}
MnemonicWasNotShown = SettingField{
reactFieldName: "mnemonic-was-not-shown?",
dBColumnName: "mnemonic_was_not_shown",
valueHandler: BoolHandler,
}
SettingFieldRegister = []SettingField{
AnonMetricsShouldSend,
Appearance,
AutoMessageEnabled,
BackupEnabled,
BackupFetched,
ChaosMode,
Currency,
CurrentUserStatus,
CustomBootNodes,
CustomBootNodesEnabled,
DappsAddress,
DefaultSyncPeriod,
DeviceName,
DisplayName,
Bio,
EIP1581Address,
Fleet,
GifAPIKey,
GifFavourites,
GifRecents,
HideHomeTooltip,
KeycardInstanceUID,
KeycardPairedOn,
KeycardPairing,
LastBackup,
LastUpdated,
LatestDerivedPath,
LinkPreviewRequestEnabled,
LinkPreviewsEnabledSites,
LogLevel,
MessagesFromContactsOnly,
Mnemonic,
MnemonicRemoved,
MutualContactEnabled,
Name,
NetworksCurrentNetwork,
NetworksNetworks,
NodeConfig,
NotificationsEnabled,
OpenseaEnabled,
PhotoPath,
PinnedMailservers,
PreferredName,
PreviewPrivacy,
ProfilePicturesShowTo,
ProfilePicturesVisibility,
PublicKey,
PushNotificationsBlockMentions,
PushNotificationsFromContactsOnly,
PushNotificationsServerEnabled,
RememberSyncingChoice,
RemotePushNotificationsEnabled,
SendPushNotifications,
SendStatusUpdates,
StickersPacksInstalled,
StickersPacksPending,
StickersRecentStickers,
SyncingOnMobileNetwork,
TelemetryServerURL,
TestNetworksEnabled,
UseMailservers,
WakuBloomFilterMode,
WalletRootAddress,
WalletSetUpPassed,
WalletVisibleTokens,
WebviewAllowPermissionRequests,
ProfileMigrationNeeded,
IsSepoliaEnabled,
TokenGroupByCommunity,
ShowCommunityAssetWhenSendingTokens,
DisplayAssetsBelowBalance,
DisplayAssetsBelowBalanceThreshold,
CollectibleGroupByCollection,
CollectibleGroupByCommunity,
URLUnfurlingMode,
}
)
func GetFieldFromProtobufType(pbt protobuf.SyncSetting_Type) (SettingField, error) {
if pbt == protobuf.SyncSetting_UNKNOWN {
return SettingField{}, errors.ErrUnrecognisedSyncSettingProtobufType
}
for _, s := range SettingFieldRegister {
if s.SyncProtobufFactory() == nil {
continue
}
if s.SyncProtobufFactory().SyncSettingProtobufType() == pbt {
return s, nil
}
}
return SettingField{}, errors.ErrUnrecognisedSyncSettingProtobufType
}

View File

@@ -0,0 +1,831 @@
package settings
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/common/dbsetup"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/errors"
"github.com/status-im/status-go/nodecfg"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/sqlite"
)
type Notifier func(SettingField, interface{})
var (
// dbInstances holds a map of singleton instances of Database
dbInstances map[string]*Database
// mutex guards the instantiation of the dbInstances values, to prevent any concurrent instantiations
mutex sync.Mutex
)
// Database sql wrapper for operations with browser objects.
type Database struct {
db *sql.DB
SyncQueue chan SyncSettingField
changesSubscriptions []chan *SyncSettingField
notifier Notifier
}
// MakeNewDB ensures that a singleton instance of Database is returned per sqlite db file
func MakeNewDB(db *sql.DB) (*Database, error) {
filename, err := dbsetup.GetDBFilename(db)
if err != nil {
return nil, err
}
d := &Database{
db: db,
SyncQueue: make(chan SyncSettingField, 100),
}
// An empty filename means that the sqlite database is held in memory
// In this case we don't want to restrict the instantiation
if filename == "" {
return d, nil
}
// Lock to protect the map from concurrent access
mutex.Lock()
defer mutex.Unlock()
// init dbInstances if it hasn't been already
if dbInstances == nil {
dbInstances = map[string]*Database{}
}
// If we haven't seen this database file before make an instance
if _, ok := dbInstances[filename]; !ok {
dbInstances[filename] = d
}
// Check if the current dbInstance is closed, if closed assign new Database
if err := dbInstances[filename].db.Ping(); err != nil {
dbInstances[filename] = d
}
return dbInstances[filename], nil
}
func (db *Database) GetDB() *sql.DB {
return db.db
}
func (db *Database) GetSyncQueue() chan SyncSettingField {
return db.SyncQueue
}
func (db *Database) GetChangesSubscriptions() []chan *SyncSettingField {
return db.changesSubscriptions
}
func (db *Database) GetNotifier() Notifier {
return db.notifier
}
func (db *Database) SetSettingsNotifier(n Notifier) {
db.notifier = n
}
// TODO remove photoPath from settings
func (db *Database) CreateSettings(s Settings, n params.NodeConfig) error {
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
_, err = tx.Exec(`
INSERT INTO settings (
address,
currency,
current_network,
dapps_address,
device_name,
preferred_name,
display_name,
bio,
eip1581_address,
installation_id,
key_uid,
keycard_instance_uid,
keycard_paired_on,
keycard_pairing,
latest_derived_path,
mnemonic,
name,
networks,
photo_path,
preview_privacy,
public_key,
signing_phrase,
wallet_root_address,
synthetic_id,
current_user_status,
profile_pictures_show_to,
profile_pictures_visibility,
url_unfurling_mode,
omit_transfers_history_scan,
mnemonic_was_not_shown,
wallet_token_preferences_group_by_community,
wallet_show_community_asset_when_sending_tokens,
wallet_display_assets_below_balance,
wallet_display_assets_below_balance_threshold,
wallet_collectible_preferences_group_by_collection,
wallet_collectible_preferences_group_by_community
) VALUES (
?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,'id',?,?,?,?,?,?,?,?,?,?,?,?)`,
s.Address,
s.Currency,
s.CurrentNetwork,
s.DappsAddress,
s.DeviceName,
s.PreferredName,
s.DisplayName,
s.Bio,
s.EIP1581Address,
s.InstallationID,
s.KeyUID,
s.KeycardInstanceUID,
s.KeycardPairedOn,
s.KeycardPairing,
s.LatestDerivedPath,
s.Mnemonic,
s.Name,
s.Networks,
s.PhotoPath,
s.PreviewPrivacy,
s.PublicKey,
s.SigningPhrase,
s.WalletRootAddress,
s.CurrentUserStatus,
s.ProfilePicturesShowTo,
s.ProfilePicturesVisibility,
s.URLUnfurlingMode,
s.OmitTransfersHistoryScan,
s.MnemonicWasNotShown,
s.TokenGroupByCommunity,
s.ShowCommunityAssetWhenSendingTokens,
s.DisplayAssetsBelowBalance,
s.DisplayAssetsBelowBalanceThreshold,
s.CollectibleGroupByCollection,
s.CollectibleGroupByCommunity,
)
if err != nil {
return err
}
if s.DisplayName != "" {
now := time.Now().Unix()
query := db.buildUpdateSyncClockQueryForField(DisplayName)
_, err := tx.Exec(query, uint64(now), uint64(now))
if err != nil {
return err
}
}
return nodecfg.SaveConfigWithTx(tx, &n)
}
func (db *Database) getSettingFieldFromReactName(reactName string) (SettingField, error) {
for _, s := range SettingFieldRegister {
if s.GetReactName() == reactName {
return s, nil
}
}
return SettingField{}, errors.ErrInvalidConfig
}
func (db *Database) makeSelectRow(setting SettingField) *sql.Row {
query := "SELECT %s FROM settings WHERE synthetic_id = 'id'"
query = fmt.Sprintf(query, setting.GetDBName())
return db.db.QueryRow(query)
}
func (db *Database) makeSelectString(setting SettingField) (string, error) {
var result sql.NullString
err := db.makeSelectRow(setting).Scan(&result)
if err == sql.ErrNoRows {
return "", nil
}
if result.Valid {
return result.String, nil
}
return "", err
}
func (db *Database) saveSetting(setting SettingField, value interface{}) error {
query := "UPDATE settings SET %s = ? WHERE synthetic_id = 'id'"
query = fmt.Sprintf(query, setting.GetDBName())
update, err := db.db.Prepare(query)
if err != nil {
return err
}
_, err = update.Exec(value)
if err != nil {
return err
}
if db.notifier != nil {
db.notifier(setting, value)
}
return nil
}
func (db *Database) parseSaveAndSyncSetting(sf SettingField, value interface{}) (err error) {
if sf.ValueHandler() != nil {
value, err = sf.ValueHandler()(value)
if err != nil {
return err
}
}
// TODO(samyoul) this is ugly as hell need a more elegant solution
if NodeConfig.GetReactName() == sf.GetReactName() {
if err = nodecfg.SaveNodeConfig(db.db, value.(*params.NodeConfig)); err != nil {
return err
}
value = nil
}
err = db.saveSetting(sf, value)
if err != nil {
return err
}
if sf.GetDBName() == DBColumnMnemonic {
mnemonicRemoved := value == nil || value.(string) == ""
err = db.saveSetting(MnemonicRemoved, mnemonicRemoved)
if err != nil {
return err
}
sf = MnemonicRemoved
value = mnemonicRemoved
}
if sf.CanSync(FromInterface) {
db.SyncQueue <- SyncSettingField{sf, value}
}
db.postChangesToSubscribers(&SyncSettingField{sf, value})
return nil
}
// SaveSetting stores data from any non-sync source
// If the field requires syncing the field data is pushed on to the SyncQueue
func (db *Database) SaveSetting(setting string, value interface{}) error {
sf, err := db.getSettingFieldFromReactName(setting)
if err != nil {
return err
}
return db.parseSaveAndSyncSetting(sf, value)
}
// SaveSettingField is identical in functionality to SaveSetting, except the setting parameter is a SettingField and
// doesn't require any SettingFieldRegister lookup.
// This func is useful if you already know the SettingField to save
func (db *Database) SaveSettingField(sf SettingField, value interface{}) error {
return db.parseSaveAndSyncSetting(sf, value)
}
func (db *Database) DeleteMnemonic() error {
return db.saveSetting(Mnemonic, nil)
}
// SaveSyncSetting stores setting data from a sync protobuf source, note it does not call SettingField.ValueHandler()
// nor does this function attempt to write to the Database.SyncQueue,
// yet it still writes to Database.changesSubscriptions.
func (db *Database) SaveSyncSetting(setting SettingField, value interface{}, clock uint64) error {
ls, err := db.GetSettingLastSynced(setting)
if err != nil {
return err
}
if clock <= ls {
return errors.ErrNewClockOlderThanCurrent
}
err = db.SetSettingLastSynced(setting, clock)
if err != nil {
return err
}
err = db.saveSetting(setting, value)
if err != nil {
return err
}
db.postChangesToSubscribers(&SyncSettingField{setting, value})
return nil
}
func (db *Database) GetSettingLastSynced(setting SettingField) (result uint64, err error) {
query := "SELECT %s FROM settings_sync_clock WHERE synthetic_id = 'id'"
query = fmt.Sprintf(query, setting.GetDBName())
err = db.db.QueryRow(query).Scan(&result)
if err != nil {
return 0, err
}
return result, nil
}
func (db *Database) buildUpdateSyncClockQueryForField(setting SettingField) string {
query := "UPDATE settings_sync_clock SET %s = ? WHERE synthetic_id = 'id' AND %s < ?"
return fmt.Sprintf(query, setting.GetDBName(), setting.GetDBName())
}
func (db *Database) SetSettingLastSynced(setting SettingField, clock uint64) error {
query := db.buildUpdateSyncClockQueryForField(setting)
_, err := db.db.Exec(query, clock, clock)
return err
}
func (db *Database) GetSettings() (Settings, error) {
var s Settings
err := db.db.QueryRow(`
SELECT
address, anon_metrics_should_send, chaos_mode, currency, current_network,
custom_bootnodes, custom_bootnodes_enabled, dapps_address, display_name, bio, eip1581_address, fleet,
hide_home_tooltip, installation_id, key_uid, keycard_instance_uid, keycard_paired_on, keycard_pairing,
last_updated, latest_derived_path, link_preview_request_enabled, link_previews_enabled_sites, log_level,
mnemonic, mnemonic_removed, name, networks, notifications_enabled, push_notifications_server_enabled,
push_notifications_from_contacts_only, remote_push_notifications_enabled, send_push_notifications,
push_notifications_block_mentions, photo_path, pinned_mailservers, preferred_name, preview_privacy, public_key,
remember_syncing_choice, signing_phrase, stickers_packs_installed, stickers_packs_pending, stickers_recent_stickers,
syncing_on_mobile_network, default_sync_period, use_mailservers, messages_from_contacts_only, usernames, appearance,
profile_pictures_show_to, profile_pictures_visibility, wallet_root_address, wallet_set_up_passed, wallet_visible_tokens,
waku_bloom_filter_mode, webview_allow_permission_requests, current_user_status, send_status_updates, gif_recents,
gif_favorites, opensea_enabled, last_backup, backup_enabled, telemetry_server_url, auto_message_enabled, gif_api_key,
test_networks_enabled, mutual_contact_enabled, profile_migration_needed, is_sepolia_enabled, wallet_token_preferences_group_by_community, url_unfurling_mode,
omit_transfers_history_scan, mnemonic_was_not_shown, wallet_show_community_asset_when_sending_tokens, wallet_display_assets_below_balance,
wallet_display_assets_below_balance_threshold, wallet_collectible_preferences_group_by_collection, wallet_collectible_preferences_group_by_community
FROM
settings
WHERE
synthetic_id = 'id'`).Scan(
&s.Address,
&s.AnonMetricsShouldSend,
&s.ChaosMode,
&s.Currency,
&s.CurrentNetwork,
&s.CustomBootnodes,
&s.CustomBootnodesEnabled,
&s.DappsAddress,
&s.DisplayName,
&s.Bio,
&s.EIP1581Address,
&s.Fleet,
&s.HideHomeTooltip,
&s.InstallationID,
&s.KeyUID,
&s.KeycardInstanceUID,
&s.KeycardPairedOn,
&s.KeycardPairing,
&s.LastUpdated,
&s.LatestDerivedPath,
&s.LinkPreviewRequestEnabled,
&s.LinkPreviewsEnabledSites,
&s.LogLevel,
&s.Mnemonic,
&s.MnemonicRemoved,
&s.Name,
&s.Networks,
&s.NotificationsEnabled,
&s.PushNotificationsServerEnabled,
&s.PushNotificationsFromContactsOnly,
&s.RemotePushNotificationsEnabled,
&s.SendPushNotifications,
&s.PushNotificationsBlockMentions,
&s.PhotoPath,
&s.PinnedMailserver,
&s.PreferredName,
&s.PreviewPrivacy,
&s.PublicKey,
&s.RememberSyncingChoice,
&s.SigningPhrase,
&s.StickerPacksInstalled,
&s.StickerPacksPending,
&s.StickersRecentStickers,
&s.SyncingOnMobileNetwork,
&s.DefaultSyncPeriod,
&s.UseMailservers,
&s.MessagesFromContactsOnly,
&s.Usernames,
&s.Appearance,
&s.ProfilePicturesShowTo,
&s.ProfilePicturesVisibility,
&s.WalletRootAddress,
&s.WalletSetUpPassed,
&s.WalletVisibleTokens,
&s.WakuBloomFilterMode,
&s.WebViewAllowPermissionRequests,
&sqlite.JSONBlob{Data: &s.CurrentUserStatus},
&s.SendStatusUpdates,
&sqlite.JSONBlob{Data: &s.GifRecents},
&sqlite.JSONBlob{Data: &s.GifFavorites},
&s.OpenseaEnabled,
&s.LastBackup,
&s.BackupEnabled,
&s.TelemetryServerURL,
&s.AutoMessageEnabled,
&s.GifAPIKey,
&s.TestNetworksEnabled,
&s.MutualContactEnabled,
&s.ProfileMigrationNeeded,
&s.IsSepoliaEnabled,
&s.TokenGroupByCommunity,
&s.URLUnfurlingMode,
&s.OmitTransfersHistoryScan,
&s.MnemonicWasNotShown,
&s.ShowCommunityAssetWhenSendingTokens,
&s.DisplayAssetsBelowBalance,
&s.DisplayAssetsBelowBalanceThreshold,
&s.CollectibleGroupByCollection,
&s.CollectibleGroupByCommunity,
)
return s, err
}
// We should remove this and realated things once mobile team starts usign `settings_notifications` package
func (db *Database) GetNotificationsEnabled() (result bool, err error) {
err = db.makeSelectRow(NotificationsEnabled).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetProfilePicturesVisibility() (result int, err error) {
err = db.makeSelectRow(ProfilePicturesVisibility).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetPublicKey() (string, error) {
return db.makeSelectString(PublicKey)
}
func (db *Database) GetFleet() (string, error) {
return db.makeSelectString(Fleet)
}
func (db *Database) GetDappsAddress() (rst types.Address, err error) {
err = db.makeSelectRow(DappsAddress).Scan(&rst)
if err == sql.ErrNoRows {
return rst, nil
}
return
}
func (db *Database) GetPinnedMailservers() (rst map[string]string, err error) {
rst = make(map[string]string)
var pinnedMailservers string
err = db.db.QueryRow("SELECT COALESCE(pinned_mailservers, '') FROM settings WHERE synthetic_id = 'id'").Scan(&pinnedMailservers)
if err == sql.ErrNoRows || pinnedMailservers == "" {
return rst, nil
}
err = json.Unmarshal([]byte(pinnedMailservers), &rst)
if err != nil {
return nil, err
}
return
}
func (db *Database) CanUseMailservers() (result bool, err error) {
err = db.makeSelectRow(UseMailservers).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) CanSyncOnMobileNetwork() (result bool, err error) {
err = db.makeSelectRow(SyncingOnMobileNetwork).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetDefaultSyncPeriod() (result uint32, err error) {
err = db.makeSelectRow(DefaultSyncPeriod).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetMessagesFromContactsOnly() (result bool, err error) {
err = db.makeSelectRow(MessagesFromContactsOnly).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetProfilePicturesShowTo() (result int64, err error) {
err = db.makeSelectRow(ProfilePicturesShowTo).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetLatestDerivedPath() (result uint, err error) {
err = db.makeSelectRow(LatestDerivedPath).Scan(&result)
return
}
func (db *Database) GetCurrentStatus(status interface{}) error {
err := db.makeSelectRow(CurrentUserStatus).Scan(&sqlite.JSONBlob{Data: &status})
if err == sql.ErrNoRows {
return nil
}
return err
}
func (db *Database) ShouldBroadcastUserStatus() (result bool, err error) {
err = db.makeSelectRow(SendStatusUpdates).Scan(&result)
// If the `send_status_updates` value is nil the sql.ErrNoRows will be returned
// because this feature is opt out, `true` should be returned in the case where no value is found
if err == sql.ErrNoRows {
return true, nil
}
return result, err
}
func (db *Database) BackupEnabled() (result bool, err error) {
err = db.makeSelectRow(BackupEnabled).Scan(&result)
if err == sql.ErrNoRows {
return true, nil
}
return result, err
}
func (db *Database) AutoMessageEnabled() (result bool, err error) {
err = db.makeSelectRow(AutoMessageEnabled).Scan(&result)
if err == sql.ErrNoRows {
return true, nil
}
return result, err
}
func (db *Database) LastBackup() (result uint64, err error) {
err = db.makeSelectRow(LastBackup).Scan(&result)
if err == sql.ErrNoRows {
return 0, nil
}
return result, err
}
func (db *Database) SetLastBackup(time uint64) error {
return db.SaveSettingField(LastBackup, time)
}
func (db *Database) SetBackupFetched(fetched bool) error {
return db.SaveSettingField(BackupFetched, fetched)
}
func (db *Database) BackupFetched() (result bool, err error) {
err = db.makeSelectRow(BackupFetched).Scan(&result)
if err == sql.ErrNoRows {
return true, nil
}
return result, err
}
func (db *Database) ENSName() (string, error) {
return db.makeSelectString(PreferredName)
}
func (db *Database) DeviceName() (string, error) {
return db.makeSelectString(DeviceName)
}
func (db *Database) DisplayName() (string, error) {
return db.makeSelectString(DisplayName)
}
func (db *Database) Bio() (string, error) {
return db.makeSelectString(Bio)
}
func (db *Database) Mnemonic() (string, error) {
return db.makeSelectString(Mnemonic)
}
func (db *Database) MnemonicRemoved() (result bool, err error) {
err = db.makeSelectRow(MnemonicRemoved).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetMnemonicWasNotShown() (result bool, err error) {
err = db.makeSelectRow(MnemonicWasNotShown).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GifAPIKey() (string, error) {
return db.makeSelectString(GifAPIKey)
}
func (db *Database) MutualContactEnabled() (result bool, err error) {
err = db.makeSelectRow(MutualContactEnabled).Scan(&result)
return result, err
}
func (db *Database) GifRecents() (recents json.RawMessage, err error) {
err = db.makeSelectRow(GifRecents).Scan(&sqlite.JSONBlob{Data: &recents})
if err == sql.ErrNoRows {
return nil, err
}
return recents, nil
}
func (db *Database) GifFavorites() (favorites json.RawMessage, err error) {
err = db.makeSelectRow(GifFavourites).Scan(&sqlite.JSONBlob{Data: &favorites})
if err == sql.ErrNoRows {
return nil, err
}
return favorites, nil
}
func (db *Database) GetPreferredUsername() (string, error) {
return db.makeSelectString(PreferredName)
}
func (db *Database) GetCurrency() (string, error) {
return db.makeSelectString(Currency)
}
func (db *Database) GetInstalledStickerPacks() (rst *json.RawMessage, err error) {
err = db.makeSelectRow(StickersPacksInstalled).Scan(&rst)
return
}
func (db *Database) GetPendingStickerPacks() (rst *json.RawMessage, err error) {
err = db.makeSelectRow(StickersPacksPending).Scan(&rst)
return
}
func (db *Database) GetRecentStickers() (rst *json.RawMessage, err error) {
err = db.makeSelectRow(StickersRecentStickers).Scan(&rst)
return
}
func (db *Database) SetPinnedMailservers(mailservers map[string]string) error {
return db.SaveSettingField(PinnedMailservers, mailservers)
}
func (db *Database) SetUseMailservers(value bool) error {
return db.SaveSettingField(UseMailservers, value)
}
func (db *Database) GetWalletRootAddress() (rst types.Address, err error) {
err = db.makeSelectRow(WalletRootAddress).Scan(&rst)
if err == sql.ErrNoRows {
return rst, nil
}
return
}
func (db *Database) GetEIP1581Address() (rst types.Address, err error) {
err = db.makeSelectRow(EIP1581Address).Scan(&rst)
if err == sql.ErrNoRows {
return rst, nil
}
return
}
func (db *Database) GetMasterAddress() (rst types.Address, err error) {
err = db.makeSelectRow(MasterAddress).Scan(&rst)
if err == sql.ErrNoRows {
return rst, nil
}
return
}
func (db *Database) GetTestNetworksEnabled() (result bool, err error) {
err = db.makeSelectRow(TestNetworksEnabled).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetIsSepoliaEnabled() (result bool, err error) {
err = db.makeSelectRow(IsSepoliaEnabled).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetTokenGroupByCommunity() (result bool, err error) {
err = db.makeSelectRow(TokenGroupByCommunity).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) SetTokenGroupByCommunity(value bool) error {
return db.SaveSettingField(TokenGroupByCommunity, value)
}
func (db *Database) GetCollectibleGroupByCollection() (result bool, err error) {
err = db.makeSelectRow(CollectibleGroupByCollection).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) SetCollectibleGroupByCollection(value bool) error {
return db.SaveSettingField(CollectibleGroupByCollection, value)
}
func (db *Database) GetCollectibleGroupByCommunity() (result bool, err error) {
err = db.makeSelectRow(CollectibleGroupByCommunity).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) SetCollectibleGroupByCommunity(value bool) error {
return db.SaveSettingField(CollectibleGroupByCommunity, value)
}
func (db *Database) GetTelemetryServerURL() (string, error) {
return db.makeSelectString(TelemetryServerURL)
}
func (db *Database) ProfileMigrationNeeded() (result bool, err error) {
err = db.makeSelectRow(ProfileMigrationNeeded).Scan(&result)
return result, err
}
func (db *Database) URLUnfurlingMode() (result int64, err error) {
err = db.makeSelectRow(URLUnfurlingMode).Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) SubscribeToChanges() chan *SyncSettingField {
s := make(chan *SyncSettingField, 100)
db.changesSubscriptions = append(db.changesSubscriptions, s)
return s
}
func (db *Database) postChangesToSubscribers(change *SyncSettingField) {
// Publish on channels, drop if buffer is full
for _, s := range db.changesSubscriptions {
select {
case s <- change:
default:
log.Warn("settings changes subscription channel full, dropping message")
}
}
}
func (db *Database) MnemonicWasShown() error {
return db.SaveSettingField(MnemonicWasNotShown, false)
}

View File

@@ -0,0 +1,79 @@
package settings
import (
"database/sql"
"encoding/json"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
)
type DatabaseSettingsManager interface {
GetDB() *sql.DB
GetSyncQueue() chan SyncSettingField
GetChangesSubscriptions() []chan *SyncSettingField
GetNotifier() Notifier
GetSettingLastSynced(setting SettingField) (result uint64, err error)
GetSettings() (Settings, error)
GetNotificationsEnabled() (result bool, err error)
GetProfilePicturesVisibility() (result int, err error)
GetPublicKey() (string, error)
GetFleet() (string, error)
GetDappsAddress() (rst types.Address, err error)
GetPinnedMailservers() (rst map[string]string, err error)
GetDefaultSyncPeriod() (result uint32, err error)
GetMessagesFromContactsOnly() (result bool, err error)
GetProfilePicturesShowTo() (result int64, err error)
GetLatestDerivedPath() (result uint, err error)
GetCurrentStatus(status interface{}) error
GetMnemonicWasNotShown() (result bool, err error)
GetPreferredUsername() (string, error)
GetCurrency() (string, error)
GetInstalledStickerPacks() (rst *json.RawMessage, err error)
GetPendingStickerPacks() (rst *json.RawMessage, err error)
GetRecentStickers() (rst *json.RawMessage, err error)
GetWalletRootAddress() (rst types.Address, err error)
GetEIP1581Address() (rst types.Address, err error)
GetMasterAddress() (rst types.Address, err error)
GetTestNetworksEnabled() (result bool, err error)
GetIsSepoliaEnabled() (result bool, err error)
GetTokenGroupByCommunity() (result bool, err error)
GetCollectibleGroupByCommunity() (result bool, err error)
GetCollectibleGroupByCollection() (result bool, err error)
GetTelemetryServerURL() (string, error)
SetSettingsNotifier(n Notifier)
SetSettingLastSynced(setting SettingField, clock uint64) error
SetLastBackup(time uint64) error
SetBackupFetched(fetched bool) error
SetPinnedMailservers(mailservers map[string]string) error
SetUseMailservers(value bool) error
SetTokenGroupByCommunity(value bool) error
CreateSettings(s Settings, n params.NodeConfig) error
SaveSetting(setting string, value interface{}) error
SaveSettingField(sf SettingField, value interface{}) error
DeleteMnemonic() error
SaveSyncSetting(setting SettingField, value interface{}, clock uint64) error
CanUseMailservers() (result bool, err error)
CanSyncOnMobileNetwork() (result bool, err error)
ShouldBroadcastUserStatus() (result bool, err error)
BackupEnabled() (result bool, err error)
AutoMessageEnabled() (result bool, err error)
LastBackup() (result uint64, err error)
BackupFetched() (result bool, err error)
ENSName() (string, error)
DeviceName() (string, error)
DisplayName() (string, error)
Bio() (string, error)
Mnemonic() (string, error)
MnemonicRemoved() (result bool, err error)
GifAPIKey() (string, error)
MutualContactEnabled() (result bool, err error)
GifRecents() (recents json.RawMessage, err error)
GifFavorites() (favorites json.RawMessage, err error)
ProfileMigrationNeeded() (result bool, err error)
URLUnfurlingMode() (result int64, err error)
SubscribeToChanges() chan *SyncSettingField
MnemonicWasShown() error
}

View File

@@ -0,0 +1,32 @@
package settings
type SyncSource int
const (
FromInterface SyncSource = iota + 1
FromStruct
)
type ProfilePicturesVisibilityType int
const (
ProfilePicturesVisibilityContactsOnly ProfilePicturesVisibilityType = iota + 1
ProfilePicturesVisibilityEveryone
ProfilePicturesVisibilityNone
)
type ProfilePicturesShowToType int
const (
ProfilePicturesShowToContactsOnly ProfilePicturesShowToType = iota + 1
ProfilePicturesShowToEveryone
ProfilePicturesShowToNone
)
type URLUnfurlingModeType int
const (
URLUnfurlingAlwaysAsk URLUnfurlingModeType = iota + 1
URLUnfurlingEnableAll
URLUnfurlingDisableAll
)

View File

@@ -0,0 +1,226 @@
package settings
import (
"encoding/json"
"reflect"
accountJson "github.com/status-im/status-go/account/json"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
type ValueHandler func(interface{}) (interface{}, error)
type SyncSettingProtobufFactoryInterface func(interface{}, uint64, string) (*common.RawMessage, *protobuf.SyncSetting, error)
type SyncSettingProtobufFactoryStruct func(Settings, uint64, string) (*common.RawMessage, *protobuf.SyncSetting, error)
type SyncSettingProtobufToValue func(setting *protobuf.SyncSetting) interface{}
// SyncProtobufFactory represents a collection of functionality to generate and parse *protobuf.SyncSetting
type SyncProtobufFactory struct {
inactive bool
fromInterface SyncSettingProtobufFactoryInterface
fromStruct SyncSettingProtobufFactoryStruct
valueFromProtobuf SyncSettingProtobufToValue
protobufType protobuf.SyncSetting_Type
}
func (spf *SyncProtobufFactory) Inactive() bool {
return spf.inactive
}
func (spf *SyncProtobufFactory) FromInterface() SyncSettingProtobufFactoryInterface {
return spf.fromInterface
}
func (spf *SyncProtobufFactory) FromStruct() SyncSettingProtobufFactoryStruct {
return spf.fromStruct
}
func (spf *SyncProtobufFactory) ExtractValueFromProtobuf() SyncSettingProtobufToValue {
return spf.valueFromProtobuf
}
func (spf *SyncProtobufFactory) SyncSettingProtobufType() protobuf.SyncSetting_Type {
return spf.protobufType
}
// SyncSettingField represents a binding between a Value and a SettingField
type SyncSettingField struct {
SettingField
Value interface{}
}
func (s SyncSettingField) MarshalJSON() ([]byte, error) {
alias := struct {
Name string `json:"name"`
Value interface{} `json:"value"`
}{
s.reactFieldName,
s.Value,
}
return json.Marshal(alias)
}
// SettingField represents an individual setting in the database, it contains context dependant names and optional
// pre-store value parsing, along with optional *SyncProtobufFactory
type SettingField struct {
reactFieldName string
dBColumnName string
valueHandler ValueHandler
syncProtobufFactory *SyncProtobufFactory
}
func (s SettingField) GetReactName() string {
return s.reactFieldName
}
func (s SettingField) GetDBName() string {
return s.dBColumnName
}
func (s SettingField) ValueHandler() ValueHandler {
return s.valueHandler
}
func (s SettingField) SyncProtobufFactory() *SyncProtobufFactory {
return s.syncProtobufFactory
}
// CanSync checks if a SettingField has functions supporting the syncing of
func (s SettingField) CanSync(source SyncSource) bool {
spf := s.syncProtobufFactory
if spf == nil {
return false
}
if spf.inactive {
return false
}
switch source {
case FromInterface:
return spf.fromInterface != nil
case FromStruct:
return spf.fromStruct != nil
default:
return false
}
}
func (s SettingField) Equals(other SettingField) bool {
return s.reactFieldName == other.reactFieldName
}
// Settings represents the entire setting row stored in the application db
type Settings struct {
// required
Address types.Address `json:"address"`
AnonMetricsShouldSend bool `json:"anon-metrics/should-send?,omitempty"`
ChaosMode bool `json:"chaos-mode?,omitempty"`
Currency string `json:"currency,omitempty"`
CurrentNetwork string `json:"networks/current-network"`
CustomBootnodes *json.RawMessage `json:"custom-bootnodes,omitempty"`
CustomBootnodesEnabled *json.RawMessage `json:"custom-bootnodes-enabled?,omitempty"`
DappsAddress types.Address `json:"dapps-address"`
DeviceName string `json:"device-name"`
DisplayName string `json:"display-name"`
Bio string `json:"bio,omitempty"`
EIP1581Address types.Address `json:"eip1581-address"`
Fleet *string `json:"fleet,omitempty"`
HideHomeTooltip bool `json:"hide-home-tooltip?,omitempty"`
InstallationID string `json:"installation-id"`
KeyUID string `json:"key-uid"`
KeycardInstanceUID string `json:"keycard-instance-uid,omitempty"`
KeycardPairedOn int64 `json:"keycard-paired-on,omitempty"`
KeycardPairing string `json:"keycard-pairing,omitempty"`
LastUpdated *int64 `json:"last-updated,omitempty"`
LatestDerivedPath uint `json:"latest-derived-path"`
LinkPreviewRequestEnabled bool `json:"link-preview-request-enabled,omitempty"`
LinkPreviewsEnabledSites *json.RawMessage `json:"link-previews-enabled-sites,omitempty"`
LogLevel *string `json:"log-level,omitempty"`
MessagesFromContactsOnly bool `json:"messages-from-contacts-only"`
Mnemonic *string `json:"mnemonic,omitempty"`
// NOTE(rasom): negation here because it safer/simpler to have false by default
MnemonicWasNotShown bool `json:"mnemonic-was-not-shown?,omitempty"`
MnemonicRemoved bool `json:"mnemonic-removed?,omitempty"`
OmitTransfersHistoryScan bool `json:"omit-transfers-history-scan?,omitempty"`
MutualContactEnabled bool `json:"mutual-contact-enabled?"`
Name string `json:"name,omitempty"`
Networks *json.RawMessage `json:"networks/networks"`
// NotificationsEnabled indicates whether local notifications should be enabled (android only)
NotificationsEnabled bool `json:"notifications-enabled?,omitempty"`
PhotoPath string `json:"photo-path"`
PinnedMailserver *json.RawMessage `json:"pinned-mailservers,omitempty"`
PreferredName *string `json:"preferred-name,omitempty"`
PreviewPrivacy bool `json:"preview-privacy?"`
PublicKey string `json:"public-key"`
// PushNotificationsServerEnabled indicates whether we should be running a push notification server
PushNotificationsServerEnabled bool `json:"push-notifications-server-enabled?,omitempty"`
// PushNotificationsFromContactsOnly indicates whether we should only receive push notifications from contacts
PushNotificationsFromContactsOnly bool `json:"push-notifications-from-contacts-only?,omitempty"`
// PushNotificationsBlockMentions indicates whether we should receive notifications for mentions
PushNotificationsBlockMentions bool `json:"push-notifications-block-mentions?,omitempty"`
RememberSyncingChoice bool `json:"remember-syncing-choice?,omitempty"`
// RemotePushNotificationsEnabled indicates whether we should be using remote notifications (ios only for now)
RemotePushNotificationsEnabled bool `json:"remote-push-notifications-enabled?,omitempty"`
SigningPhrase string `json:"signing-phrase"`
StickerPacksInstalled *json.RawMessage `json:"stickers/packs-installed,omitempty"`
StickerPacksPending *json.RawMessage `json:"stickers/packs-pending,omitempty"`
StickersRecentStickers *json.RawMessage `json:"stickers/recent-stickers,omitempty"`
SyncingOnMobileNetwork bool `json:"syncing-on-mobile-network?,omitempty"`
// DefaultSyncPeriod is how far back in seconds we should pull messages from a mailserver
DefaultSyncPeriod uint `json:"default-sync-period"`
// SendPushNotifications indicates whether we should send push notifications for other clients
SendPushNotifications bool `json:"send-push-notifications?,omitempty"`
Appearance uint `json:"appearance"`
// ProfilePicturesShowTo indicates to whom the user shows their profile picture to (contacts, everyone)
ProfilePicturesShowTo ProfilePicturesShowToType `json:"profile-pictures-show-to"`
// ProfilePicturesVisibility indicates who we want to see profile pictures of (contacts, everyone or none)
ProfilePicturesVisibility ProfilePicturesVisibilityType `json:"profile-pictures-visibility"`
UseMailservers bool `json:"use-mailservers?"`
Usernames *json.RawMessage `json:"usernames,omitempty"`
WalletRootAddress types.Address `json:"wallet-root-address,omitempty"`
WalletSetUpPassed bool `json:"wallet-set-up-passed?,omitempty"`
WalletVisibleTokens *json.RawMessage `json:"wallet/visible-tokens,omitempty"`
WakuBloomFilterMode bool `json:"waku-bloom-filter-mode,omitempty"`
WebViewAllowPermissionRequests bool `json:"webview-allow-permission-requests?,omitempty"`
SendStatusUpdates bool `json:"send-status-updates?,omitempty"`
CurrentUserStatus *json.RawMessage `json:"current-user-status"`
GifRecents *json.RawMessage `json:"gifs/recent-gifs"`
GifFavorites *json.RawMessage `json:"gifs/favorite-gifs"`
OpenseaEnabled bool `json:"opensea-enabled?,omitempty"`
TelemetryServerURL string `json:"telemetry-server-url,omitempty"`
LastBackup uint64 `json:"last-backup,omitempty"`
BackupEnabled bool `json:"backup-enabled?,omitempty"`
AutoMessageEnabled bool `json:"auto-message-enabled?,omitempty"`
GifAPIKey string `json:"gifs/api-key"`
TestNetworksEnabled bool `json:"test-networks-enabled?,omitempty"`
ProfileMigrationNeeded bool `json:"profile-migration-needed,omitempty"`
IsSepoliaEnabled bool `json:"is-sepolia-enabled?,omitempty"`
TokenGroupByCommunity bool `json:"token-group-by-community?,omitempty"`
ShowCommunityAssetWhenSendingTokens bool `json:"show-community-asset-when-sending-tokens?,omitempty"`
DisplayAssetsBelowBalance bool `json:"display-assets-below-balance?,omitempty"`
DisplayAssetsBelowBalanceThreshold int64 `json:"display-assets-below-balance-threshold,omitempty"`
CollectibleGroupByCollection bool `json:"collectible-group-by-collection?,omitempty"`
CollectibleGroupByCommunity bool `json:"collectible-group-by-community?,omitempty"`
URLUnfurlingMode URLUnfurlingModeType `json:"url-unfurling-mode,omitempty"`
}
func (s Settings) MarshalJSON() ([]byte, error) {
// We need this typedef in order to overcome stack overflow
// when marshaling JSON
type Alias Settings
ext, err := accountJson.ExtendStructWithPubKeyData(s.PublicKey, Alias(s))
if err != nil {
return nil, err
}
return json.Marshal(ext)
}
func (s Settings) IsEmpty() bool {
empty := reflect.Zero(reflect.TypeOf(s)).Interface()
return reflect.DeepEqual(s, empty)
}

View File

@@ -0,0 +1,618 @@
package settings
import (
"bytes"
"encoding/json"
"reflect"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/sqlite"
)
var (
ErrTypeAssertionFailed = errors.New("type assertion of interface value failed")
)
func buildRawSyncSettingMessage(msg *protobuf.SyncSetting, chatID string) (*common.RawMessage, error) {
encodedMessage, err := proto.Marshal(msg)
if err != nil {
return nil, err
}
return &common.RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_SETTING,
ResendAutomatically: true,
}, nil
}
// Currency
func buildRawCurrencySyncMessage(v string, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_CURRENCY,
Value: &protobuf.SyncSetting_ValueString{ValueString: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func currencyProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertString(value)
if err != nil {
return nil, nil, err
}
return buildRawCurrencySyncMessage(v, clock, chatID)
}
func currencyProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawCurrencySyncMessage(s.Currency, clock, chatID)
}
// GifFavorites
func buildRawGifFavoritesSyncMessage(v []byte, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_GIF_FAVOURITES,
Value: &protobuf.SyncSetting_ValueBytes{ValueBytes: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func gifFavouritesProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBytes(value)
if err != nil {
return nil, nil, err
}
return buildRawGifFavoritesSyncMessage(v, clock, chatID)
}
func gifFavouritesProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
gf := extractJSONRawMessage(s.GifFavorites)
return buildRawGifFavoritesSyncMessage(gf, clock, chatID)
}
// GifRecents
func buildRawGifRecentsSyncMessage(v []byte, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_GIF_RECENTS,
Value: &protobuf.SyncSetting_ValueBytes{ValueBytes: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func gifRecentsProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBytes(value)
if err != nil {
return nil, nil, err
}
return buildRawGifRecentsSyncMessage(v, clock, chatID)
}
func gifRecentsProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
gr := extractJSONRawMessage(s.GifRecents)
return buildRawGifRecentsSyncMessage(gr, clock, chatID)
}
// MessagesFromContactsOnly
func buildRawMessagesFromContactsOnlySyncMessage(v bool, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_MESSAGES_FROM_CONTACTS_ONLY,
Value: &protobuf.SyncSetting_ValueBool{ValueBool: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func messagesFromContactsOnlyProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBool(value)
if err != nil {
return nil, nil, err
}
return buildRawMessagesFromContactsOnlySyncMessage(v, clock, chatID)
}
func messagesFromContactsOnlyProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawMessagesFromContactsOnlySyncMessage(s.MessagesFromContactsOnly, clock, chatID)
}
// PreferredName
func buildRawPreferredNameSyncMessage(v string, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_PREFERRED_NAME,
Value: &protobuf.SyncSetting_ValueString{ValueString: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func preferredNameProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertString(value)
if err != nil {
return nil, nil, err
}
return buildRawPreferredNameSyncMessage(v, clock, chatID)
}
func preferredNameProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
var pn string
if s.PreferredName != nil {
pn = *s.PreferredName
}
return buildRawPreferredNameSyncMessage(pn, clock, chatID)
}
// PreviewPrivacy
func buildRawPreviewPrivacySyncMessage(v bool, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_PREVIEW_PRIVACY,
Value: &protobuf.SyncSetting_ValueBool{ValueBool: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func previewPrivacyProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBool(value)
if err != nil {
return nil, nil, err
}
return buildRawPreviewPrivacySyncMessage(v, clock, chatID)
}
func previewPrivacyProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawPreviewPrivacySyncMessage(s.PreviewPrivacy, clock, chatID)
}
// ProfilePicturesShowTo
func buildRawProfilePicturesShowToSyncMessage(v int64, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_PROFILE_PICTURES_SHOW_TO,
Value: &protobuf.SyncSetting_ValueInt64{ValueInt64: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func profilePicturesShowToProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := parseNumberToInt64(value)
if err != nil {
return nil, nil, err
}
return buildRawProfilePicturesShowToSyncMessage(v, clock, chatID)
}
func profilePicturesShowToProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawProfilePicturesShowToSyncMessage(int64(s.ProfilePicturesShowTo), clock, chatID)
}
// ProfilePicturesVisibility
func buildRawProfilePicturesVisibilitySyncMessage(v int64, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_PROFILE_PICTURES_VISIBILITY,
Value: &protobuf.SyncSetting_ValueInt64{ValueInt64: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func profilePicturesVisibilityProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := parseNumberToInt64(value)
if err != nil {
return nil, nil, err
}
return buildRawProfilePicturesVisibilitySyncMessage(v, clock, chatID)
}
func profilePicturesVisibilityProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawProfilePicturesVisibilitySyncMessage(int64(s.ProfilePicturesVisibility), clock, chatID)
}
// SendStatusUpdates
func buildRawSendStatusUpdatesSyncMessage(v bool, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_SEND_STATUS_UPDATES,
Value: &protobuf.SyncSetting_ValueBool{ValueBool: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func sendStatusUpdatesProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBool(value)
if err != nil {
return nil, nil, err
}
return buildRawSendStatusUpdatesSyncMessage(v, clock, chatID)
}
func sendStatusUpdatesProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawSendStatusUpdatesSyncMessage(s.SendStatusUpdates, clock, chatID)
}
// StickerPacksInstalled
func buildRawStickerPacksInstalledSyncMessage(v []byte, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_STICKERS_PACKS_INSTALLED,
Value: &protobuf.SyncSetting_ValueBytes{ValueBytes: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func stickersPacksInstalledProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := parseJSONBlobData(value)
if err != nil {
return nil, nil, err
}
return buildRawStickerPacksInstalledSyncMessage(v, clock, chatID)
}
func stickersPacksInstalledProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
spi := extractJSONRawMessage(s.StickerPacksInstalled)
return buildRawStickerPacksInstalledSyncMessage(spi, clock, chatID)
}
// StickerPacksPending
func buildRawStickerPacksPendingSyncMessage(v []byte, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_STICKERS_PACKS_PENDING,
Value: &protobuf.SyncSetting_ValueBytes{ValueBytes: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func stickersPacksPendingProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := parseJSONBlobData(value)
if err != nil {
return nil, nil, err
}
return buildRawStickerPacksPendingSyncMessage(v, clock, chatID)
}
func stickersPacksPendingProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
spp := extractJSONRawMessage(s.StickerPacksPending)
return buildRawStickerPacksPendingSyncMessage(spp, clock, chatID)
}
// StickersRecentStickers
func buildRawStickersRecentStickersSyncMessage(v []byte, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_STICKERS_RECENT_STICKERS,
Value: &protobuf.SyncSetting_ValueBytes{ValueBytes: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func stickersRecentStickersProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := parseJSONBlobData(value)
if err != nil {
return nil, nil, err
}
return buildRawStickersRecentStickersSyncMessage(v, clock, chatID)
}
func stickersRecentStickersProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
srs := extractJSONRawMessage(s.StickersRecentStickers)
return buildRawStickersRecentStickersSyncMessage(srs, clock, chatID)
}
func assertBytes(value interface{}) ([]byte, error) {
v, ok := value.([]byte)
if !ok {
return nil, errors.Wrapf(ErrTypeAssertionFailed, "expected '[]byte', received %T", value)
}
return v, nil
}
func assertBool(value interface{}) (bool, error) {
v, ok := value.(bool)
if !ok {
return false, errors.Wrapf(ErrTypeAssertionFailed, "expected 'bool', received %T", value)
}
return v, nil
}
func assertString(value interface{}) (string, error) {
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Ptr {
value = *value.(*string)
}
v, ok := value.(string)
if !ok {
return "", errors.Wrapf(ErrTypeAssertionFailed, "expected 'string', received %T", value)
}
return v, nil
}
func parseJSONBlobData(value interface{}) ([]byte, error) {
switch v := value.(type) {
case []byte:
return v, nil
case *sqlite.JSONBlob:
return extractJSONBlob(v)
default:
return nil, errors.Wrapf(ErrTypeAssertionFailed, "expected []byte or *sqlite.JSONBlob, received %T", value)
}
}
func parseNumberToInt64(value interface{}) (int64, error) {
switch v := value.(type) {
case float32:
return int64(v), nil
case float64:
return int64(v), nil
case int:
return int64(v), nil
case int8:
return int64(v), nil
case int16:
return int64(v), nil
case int32:
return int64(v), nil
case int64:
return v, nil
case uint:
return int64(v), nil
case uint8:
return int64(v), nil
case uint16:
return int64(v), nil
case uint32:
return int64(v), nil
case uint64:
return int64(v), nil
case ProfilePicturesShowToType:
return int64(v), nil
case ProfilePicturesVisibilityType:
return int64(v), nil
case URLUnfurlingModeType:
return int64(v), nil
default:
return 0, errors.Wrapf(ErrTypeAssertionFailed, "expected a numeric type, received %T", value)
}
}
func extractJSONBlob(jb *sqlite.JSONBlob) ([]byte, error) {
value, err := jb.Value()
if err != nil {
return nil, err
}
if value == nil {
return nil, nil
}
return value.([]byte), nil
}
func extractJSONRawMessage(jrm *json.RawMessage) []byte {
if jrm == nil {
return nil
}
out, _ := jrm.MarshalJSON() // Don't need to parse error because it is always nil
if len(out) == 0 || bytes.Equal(out, []byte("null")) {
return nil
}
return out
}
// DisplayName
func buildRawDisplayNameSyncMessage(v string, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_DISPLAY_NAME,
Value: &protobuf.SyncSetting_ValueString{ValueString: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func displayNameProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertString(value)
if err != nil {
return nil, nil, err
}
return buildRawDisplayNameSyncMessage(v, clock, chatID)
}
func displayNameProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawDisplayNameSyncMessage(s.DisplayName, clock, chatID)
}
// Bio
func buildRawBioSyncMessage(v string, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_BIO,
Value: &protobuf.SyncSetting_ValueString{ValueString: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func bioProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertString(value)
if err != nil {
return nil, nil, err
}
return buildRawBioSyncMessage(v, clock, chatID)
}
func bioProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawBioSyncMessage(s.Bio, clock, chatID)
}
// MnemonicRemoved
func buildRawMnemonicRemovedSyncMessage(v bool, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_MNEMONIC_REMOVED,
Value: &protobuf.SyncSetting_ValueBool{ValueBool: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func mnemonicRemovedProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBool(value)
if err != nil {
return nil, nil, err
}
return buildRawMnemonicRemovedSyncMessage(v, clock, chatID)
}
func mnemonicRemovedProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawMnemonicRemovedSyncMessage(s.MnemonicRemoved, clock, chatID)
}
// UrlUnfurlingMode
func buildRawURLUnfurlingModeSyncMessage(v int64, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_URL_UNFURLING_MODE,
Value: &protobuf.SyncSetting_ValueInt64{ValueInt64: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func urlUnfurlingModeProtobufFactory(value any, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := parseNumberToInt64(value)
if err != nil {
return nil, nil, err
}
return buildRawURLUnfurlingModeSyncMessage(v, clock, chatID)
}
func urlUnfurlingModeProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawURLUnfurlingModeSyncMessage(int64(s.URLUnfurlingMode), clock, chatID)
}
// ShowCommunityAssetWhenSendingTokens
func buildRawShowCommunityAssetWhenSendingTokensSyncMessage(v bool, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_SHOW_COMMUNITY_ASSET_WHEN_SENDING_TOKENS,
Value: &protobuf.SyncSetting_ValueBool{ValueBool: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func showCommunityAssetWhenSendingTokensProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBool(value)
if err != nil {
return nil, nil, err
}
return buildRawShowCommunityAssetWhenSendingTokensSyncMessage(v, clock, chatID)
}
func showCommunityAssetWhenSendingTokensProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawShowCommunityAssetWhenSendingTokensSyncMessage(s.ShowCommunityAssetWhenSendingTokens, clock, chatID)
}
// DisplayAssetsBelowBalance
func buildRawDisplayAssetsBelowBalanceSyncMessage(v bool, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_DISPLAY_ASSETS_BELOW_BALANCE,
Value: &protobuf.SyncSetting_ValueBool{ValueBool: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func displayAssetsBelowBalanceProtobufFactory(value interface{}, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := assertBool(value)
if err != nil {
return nil, nil, err
}
return buildRawDisplayAssetsBelowBalanceSyncMessage(v, clock, chatID)
}
func displayAssetsBelowBalanceProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawDisplayAssetsBelowBalanceSyncMessage(s.DisplayAssetsBelowBalance, clock, chatID)
}
// DisplayAssetsBelowBalanceThreshold
func buildRawDisplayAssetsBelowBalanceThresholdSyncMessage(v int64, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
pb := &protobuf.SyncSetting{
Type: protobuf.SyncSetting_DISPLAY_ASSETS_BELOW_BALANCE_THRESHOLD,
Value: &protobuf.SyncSetting_ValueInt64{ValueInt64: v},
Clock: clock,
}
rm, err := buildRawSyncSettingMessage(pb, chatID)
return rm, pb, err
}
func displayAssetsBelowBalanceThresholdProtobufFactory(value any, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
v, err := parseNumberToInt64(value)
if err != nil {
return nil, nil, err
}
return buildRawDisplayAssetsBelowBalanceThresholdSyncMessage(v, clock, chatID)
}
func displayAssetsBelowBalanceThresholdProtobufFactoryStruct(s Settings, clock uint64, chatID string) (*common.RawMessage, *protobuf.SyncSetting, error) {
return buildRawDisplayAssetsBelowBalanceThresholdSyncMessage(s.DisplayAssetsBelowBalanceThreshold, clock, chatID)
}

View File

@@ -0,0 +1,74 @@
package settings
import (
"encoding/json"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/errors"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/sqlite"
)
func StringFromSyncProtobuf(ss *protobuf.SyncSetting) interface{} {
return ss.GetValueString()
}
func BoolFromSyncProtobuf(ss *protobuf.SyncSetting) interface{} {
return ss.GetValueBool()
}
func BytesFromSyncProtobuf(ss *protobuf.SyncSetting) interface{} {
return ss.GetValueBytes()
}
func Int64FromSyncProtobuf(ss *protobuf.SyncSetting) interface{} {
return ss.GetValueInt64()
}
func BoolHandler(value interface{}) (interface{}, error) {
_, ok := value.(bool)
if !ok {
return value, errors.ErrInvalidConfig
}
return value, nil
}
func Int64Handler(value interface{}) (interface{}, error) {
_, ok := value.(int64)
if !ok {
return value, errors.ErrInvalidConfig
}
return value, nil
}
func JSONBlobHandler(value interface{}) (interface{}, error) {
return &sqlite.JSONBlob{Data: value}, nil
}
func AddressHandler(value interface{}) (interface{}, error) {
str, ok := value.(string)
if ok {
value = types.HexToAddress(str)
} else {
return value, errors.ErrInvalidConfig
}
return value, nil
}
func NodeConfigHandler(value interface{}) (interface{}, error) {
jsonString, err := json.Marshal(value)
if err != nil {
return nil, err
}
nodeConfig := new(params.NodeConfig)
err = json.Unmarshal(jsonString, nodeConfig)
if err != nil {
return nil, err
}
return nodeConfig, nil
}

View File

@@ -0,0 +1,315 @@
package notificationssettings
import (
"database/sql"
"fmt"
)
type settingType int
const (
stringType settingType = iota
integerType
boolType
)
// Columns' names
type column string
const (
columnTextValue column = "text_value"
columnIntValue column = "int_value"
columnBoolValue column = "bool_value"
columnExMuteAllMessages column = "ex_mute_all_messages"
columnExPersonalMentions column = "ex_personal_mentions"
columnExGlobalMentions column = "ex_global_mentions"
columnExOtherMessages column = "ex_other_messages"
)
// Static ids we use for notifications settings
const (
keyAllowNotifications = "AllowNotifications"
keyOneToOneChats = "OneToOneChats"
keyGroupChats = "GroupChats"
keyPersonalMentions = "PersonalMentions"
keyGlobalMentions = "GlobalMentions"
keyAllMessages = "AllMessages"
keyContactRequests = "ContactRequests"
keyIdentityVerificationRequests = "IdentityVerificationRequests"
keySoundEnabled = "SoundEnabled"
keyVolume = "Volume"
keyMessagePreview = "MessagePreview"
)
// Possible values
const (
valueSendAlerts = "SendAlerts"
//valueDeliverQuietly = "DeliverQuietly" // currently unused
valueTurnOff = "TurnOff"
)
// Default values
const (
defaultAllowNotificationsValue = true
defaultOneToOneChatsValue = valueSendAlerts
defaultGroupChatsValue = valueSendAlerts
defaultPersonalMentionsValue = valueSendAlerts
defaultGlobalMentionsValue = valueSendAlerts
defaultAllMessagesValue = valueTurnOff
defaultContactRequestsValue = valueSendAlerts
defaultIdentityVerificationRequestsValue = valueSendAlerts
defaultSoundEnabledValue = true
defaultVolumeValue = 50
defaultMessagePreviewValue = 2
defaultExMuteAllMessagesValue = false
defaultExPersonalMentionsValue = valueSendAlerts
defaultExGlobalMentionsValue = valueSendAlerts
defaultExOtherMessagesValue = valueTurnOff
)
type NotificationsSettings struct {
db *sql.DB
}
func NewNotificationsSettings(db *sql.DB) *NotificationsSettings {
return &NotificationsSettings{
db: db,
}
}
func (ns *NotificationsSettings) buildSelectQuery(column column) string {
return fmt.Sprintf("SELECT %s FROM notifications_settings WHERE id = ?", column)
}
func (ns *NotificationsSettings) buildInsertOrUpdateQuery(isExemption bool, settingType settingType) string {
var values string
if isExemption {
values = "VALUES(?, 1, NULL, NULL, NULL, ?, ?, ?, ?)"
} else {
switch settingType {
case stringType:
values = "VALUES(?, 0, ?, NULL, NULL, NULL, NULL, NULL, NULL)"
case integerType:
values = "VALUES(?, 0, NULL, ?, NULL, NULL, NULL, NULL, NULL)"
case boolType:
values = "VALUES(?, 0, NULL, NULL, ?, NULL, NULL, NULL, NULL)"
}
}
return fmt.Sprintf(`INSERT INTO notifications_settings (
id,
exemption,
text_value,
int_value,
bool_value,
ex_mute_all_messages,
ex_personal_mentions,
ex_global_mentions,
ex_other_messages)
%s;`, values)
}
// Non exemption settings
func (ns *NotificationsSettings) GetAllowNotifications() (result bool, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnBoolValue), keyAllowNotifications).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultAllowNotificationsValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetAllowNotifications(value bool) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, boolType), keyAllowNotifications, value, value)
return err
}
func (ns *NotificationsSettings) GetOneToOneChats() (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnTextValue), keyOneToOneChats).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultOneToOneChatsValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetOneToOneChats(value string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, stringType), keyOneToOneChats, value, value)
return err
}
func (ns *NotificationsSettings) GetGroupChats() (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnTextValue), keyGroupChats).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultGroupChatsValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetGroupChats(value string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, stringType), keyGroupChats, value, value)
return err
}
func (ns *NotificationsSettings) GetPersonalMentions() (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnTextValue), keyPersonalMentions).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultPersonalMentionsValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetPersonalMentions(value string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, stringType), keyPersonalMentions, value, value)
return err
}
func (ns *NotificationsSettings) GetGlobalMentions() (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnTextValue), keyGlobalMentions).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultGlobalMentionsValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetGlobalMentions(value string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, stringType), keyGlobalMentions, value, value)
return err
}
func (ns *NotificationsSettings) GetAllMessages() (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnTextValue), keyAllMessages).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultAllMessagesValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetAllMessages(value string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, stringType), keyAllMessages, value, value)
return err
}
func (ns *NotificationsSettings) GetContactRequests() (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnTextValue), keyContactRequests).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultContactRequestsValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetContactRequests(value string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, stringType), keyContactRequests, value, value)
return err
}
func (ns *NotificationsSettings) GetIdentityVerificationRequests() (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnTextValue), keyIdentityVerificationRequests).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultIdentityVerificationRequestsValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetIdentityVerificationRequests(value string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, stringType), keyIdentityVerificationRequests, value, value)
return err
}
func (ns *NotificationsSettings) GetSoundEnabled() (result bool, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnBoolValue), keySoundEnabled).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultSoundEnabledValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetSoundEnabled(value bool) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, boolType), keySoundEnabled, value, value)
return err
}
func (ns *NotificationsSettings) GetVolume() (result int, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnIntValue), keyVolume).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultVolumeValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetVolume(value int) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, integerType), keyVolume, value, value)
return err
}
func (ns *NotificationsSettings) GetMessagePreview() (result int, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnIntValue), keyMessagePreview).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultMessagePreviewValue, err
}
return result, err
}
func (ns *NotificationsSettings) SetMessagePreview(value int) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(false, integerType), keyMessagePreview, value, value)
return err
}
// Exemption settings
func (ns *NotificationsSettings) GetExMuteAllMessages(id string) (result bool, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnExMuteAllMessages), id).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultExMuteAllMessagesValue, nil
}
return result, err
}
func (ns *NotificationsSettings) GetExPersonalMentions(id string) (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnExPersonalMentions), id).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultExPersonalMentionsValue, nil
}
return result, err
}
func (ns *NotificationsSettings) GetExGlobalMentions(id string) (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnExGlobalMentions), id).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultExGlobalMentionsValue, nil
}
return result, err
}
func (ns *NotificationsSettings) GetExOtherMessages(id string) (result string, err error) {
err = ns.db.QueryRow(ns.buildSelectQuery(columnExOtherMessages), id).Scan(&result)
if err != nil && err == sql.ErrNoRows {
return defaultExOtherMessagesValue, nil
}
return result, err
}
func (ns *NotificationsSettings) SetExemptions(id string, muteAllMessages bool, personalMentions string,
globalMentions string, otherMessages string) error {
_, err := ns.db.Exec(ns.buildInsertOrUpdateQuery(true, stringType), id, muteAllMessages, personalMentions, globalMentions,
otherMessages)
return err
}
func (ns *NotificationsSettings) DeleteExemptions(id string) error {
switch id {
case
keyAllowNotifications,
keyOneToOneChats,
keyGroupChats,
keyPersonalMentions,
keyGlobalMentions,
keyAllMessages,
keyContactRequests,
keyIdentityVerificationRequests,
keySoundEnabled,
keyVolume,
keyMessagePreview:
return fmt.Errorf("%s, static notifications settings cannot be deleted", id)
}
_, err := ns.db.Exec("DELETE FROM notifications_settings WHERE id = ?", id)
return err
}

View File

@@ -0,0 +1,143 @@
package sociallinkssettings
import (
"context"
"database/sql"
"errors"
"github.com/status-im/status-go/protocol/identity"
)
const (
MaxNumOfSocialLinks = 20
)
var (
ErrNilSocialLinkProvided = errors.New("social links, nil object provided")
ErrOlderSocialLinksProvided = errors.New("older social links provided")
)
type SocialLinksSettings struct {
db *sql.DB
}
func NewSocialLinksSettings(db *sql.DB) *SocialLinksSettings {
return &SocialLinksSettings{
db: db,
}
}
func (s *SocialLinksSettings) getSocialLinksClock(tx *sql.Tx) (result uint64, err error) {
query := "SELECT social_links FROM settings_sync_clock WHERE synthetic_id = 'id'"
if tx == nil {
err = s.db.QueryRow(query).Scan(&result)
} else {
err = tx.QueryRow(query).Scan(&result)
}
return result, err
}
func (s *SocialLinksSettings) getSocialLinks(tx *sql.Tx) (identity.SocialLinks, error) {
var (
rows *sql.Rows
err error
)
query := "SELECT text, url FROM profile_social_links ORDER BY position ASC"
if tx == nil {
rows, err = s.db.Query(query)
} else {
rows, err = tx.Query(query)
}
if err != nil {
return nil, err
}
defer rows.Close()
var socialLinks identity.SocialLinks
for rows.Next() {
socialLink := &identity.SocialLink{}
err := rows.Scan(&socialLink.Text, &socialLink.URL)
if err != nil {
return nil, err
}
socialLinks = append(socialLinks, socialLink)
}
err = rows.Err()
if err != nil {
return nil, err
}
return socialLinks, nil
}
func (s *SocialLinksSettings) GetSocialLinks() (identity.SocialLinks, error) {
return s.getSocialLinks(nil)
}
func (s *SocialLinksSettings) GetSocialLinksClock() (result uint64, err error) {
return s.getSocialLinksClock(nil)
}
func (s *SocialLinksSettings) AddOrReplaceSocialLinksIfNewer(links identity.SocialLinks, clock uint64) error {
tx, err := s.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
dbClock, err := s.getSocialLinksClock(tx)
if err != nil {
return err
}
if dbClock > clock {
return ErrOlderSocialLinksProvided
}
dbLinks, err := s.getSocialLinks(tx)
if err != nil {
return err
}
if len(dbLinks) > 0 {
_, err = tx.Exec("DELETE from profile_social_links")
if err != nil {
return err
}
}
stmt, err := tx.Prepare("INSERT INTO profile_social_links (text, url, position) VALUES (?, ?, ?)")
if err != nil {
return err
}
defer stmt.Close()
for position, link := range links {
if link == nil {
return ErrNilSocialLinkProvided
}
_, err = stmt.Exec(
link.Text,
link.URL,
position,
)
if err != nil {
return err
}
}
stmt, err = tx.Prepare("UPDATE settings_sync_clock SET social_links = ? WHERE synthetic_id = 'id'")
if err != nil {
return err
}
_, err = stmt.Exec(clock)
return err
}

View File

@@ -0,0 +1,229 @@
package walletsettings
import (
"database/sql"
"errors"
)
type TokenPreferences struct {
Key string `json:"key"`
Position int `json:"position"`
GroupPosition int `json:"groupPosition"`
Visible bool `json:"visible"`
CommunityID string `json:"communityId"`
}
type CollectiblePreferencesType int
const (
CollectiblePreferencesTypeNonCommunityCollectible CollectiblePreferencesType = iota + 1
CollectiblePreferencesTypeCommunityCollectible
CollectiblePreferencesTypeCollection
CollectiblePreferencesTypeCommunity
)
type CollectiblePreferences struct {
Type CollectiblePreferencesType `json:"type"`
Key string `json:"key"`
Position int `json:"position"`
Visible bool `json:"visible"`
}
type WalletSettings struct {
db *sql.DB
}
func NewWalletSettings(db *sql.DB) *WalletSettings {
return &WalletSettings{
db: db,
}
}
// This function should not be used directly, it is called from the functions which update token preferences.
func (ws *WalletSettings) setClockOfLastTokenPreferencesChange(tx *sql.Tx, clock uint64) error {
if tx == nil {
return errors.New("database transaction is nil")
}
_, err := tx.Exec("UPDATE settings SET wallet_token_preferences_change_clock = ? WHERE synthetic_id = 'id'", clock)
return err
}
func (ws *WalletSettings) GetClockOfLastTokenPreferencesChange() (result uint64, err error) {
query := "SELECT wallet_token_preferences_change_clock FROM settings WHERE synthetic_id = 'id'"
err = ws.db.QueryRow(query).Scan(&result)
if err != nil {
return 0, err
}
return result, err
}
func (ws *WalletSettings) UpdateTokenPreferences(preferences []TokenPreferences, groupByCommunity bool, testNetworksEnabled bool, clock uint64) error {
if len(preferences) == 0 {
return errors.New("tokens: trying to create custom order with empty list")
}
tx, err := ws.db.Begin()
if err != nil {
return err
}
var mainError error = nil
defer func() {
if mainError == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
_, mainError = tx.Exec("DELETE FROM token_preferences WHERE testnet = ?", testNetworksEnabled)
if mainError != nil {
return mainError
}
for _, p := range preferences {
if p.Position < 0 {
mainError = errors.New("tokens: trying to create custom order with negative position")
return mainError
}
_, err := tx.Exec("INSERT INTO token_preferences (key, position, group_position, visible, community_id, testnet) VALUES (?, ?, ?, ?, ?, ?)", p.Key, p.Position, p.GroupPosition, p.Visible, p.CommunityID, testNetworksEnabled)
if err != nil {
mainError = err
return err
}
}
if groupByCommunity {
// Find community tokens without group position
// Group position can be -1 if it wasn't created yet. Values must be consitstent across all tokens
rows, err := tx.Query(`SELECT COUNT(*) FROM token_preferences WHERE testnet = ? AND group_position = -1 AND community_id != '' AND visible GROUP BY community_id HAVING COUNT(*) > 0`, testNetworksEnabled)
if err != nil {
mainError = err
return err
}
if rows.Next() {
mainError = errors.New("tokens: not all community tokens have assigned the group position")
return mainError
}
}
mainError = ws.setClockOfLastTokenPreferencesChange(tx, clock)
if mainError != nil {
return mainError
}
return nil
}
func (ws *WalletSettings) GetTokenPreferences(testNetworksEnabled bool) ([]TokenPreferences, error) {
rows, err := ws.db.Query("SELECT key, position, group_position, visible, community_id FROM token_preferences WHERE testnet = ?", testNetworksEnabled)
if err != nil {
return nil, err
}
defer rows.Close()
var result []TokenPreferences
for rows.Next() {
token := TokenPreferences{}
err := rows.Scan(&token.Key, &token.Position, &token.GroupPosition, &token.Visible, &token.CommunityID)
if err != nil {
return nil, err
}
result = append(result, token)
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (ws *WalletSettings) setClockOfLastCollectiblePreferencesChange(tx *sql.Tx, clock uint64) error {
if tx == nil {
return errors.New("database transaction is nil")
}
_, err := tx.Exec("UPDATE settings SET wallet_collectible_preferences_change_clock = ? WHERE synthetic_id = 'id'", clock)
return err
}
func (ws *WalletSettings) GetClockOfLastCollectiblePreferencesChange() (result uint64, err error) {
query := "SELECT wallet_collectible_preferences_change_clock FROM settings WHERE synthetic_id = 'id'"
err = ws.db.QueryRow(query).Scan(&result)
if err != nil {
return 0, err
}
return result, err
}
func (ws *WalletSettings) UpdateCollectiblePreferences(preferences []CollectiblePreferences, groupByCommunity bool, groupByCollection bool, testNetworksEnabled bool, clock uint64) error {
if len(preferences) == 0 {
return errors.New("collectibles: trying to create custom order with empty list")
}
tx, err := ws.db.Begin()
if err != nil {
return err
}
var mainError error = nil
defer func() {
if mainError == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
_, mainError = tx.Exec("DELETE FROM collectible_preferences WHERE testnet = ?", testNetworksEnabled)
if mainError != nil {
return mainError
}
for _, p := range preferences {
if p.Position < 0 {
mainError = errors.New("collectibles: trying to create custom order with negative position")
return mainError
}
_, err := tx.Exec("INSERT INTO collectible_preferences (type, key, position, visible, testnet) VALUES (?, ?, ?, ?, ?)", p.Type, p.Key, p.Position, p.Visible, testNetworksEnabled)
if err != nil {
mainError = err
return err
}
}
mainError = ws.setClockOfLastCollectiblePreferencesChange(tx, clock)
if mainError != nil {
return mainError
}
return nil
}
func (ws *WalletSettings) GetCollectiblePreferences(testNetworksEnabled bool) ([]CollectiblePreferences, error) {
rows, err := ws.db.Query("SELECT type, key, position, visible FROM collectible_preferences WHERE testnet = ?", testNetworksEnabled)
if err != nil {
return nil, err
}
defer rows.Close()
var result []CollectiblePreferences
for rows.Next() {
p := CollectiblePreferences{}
err := rows.Scan(&p.Type, &p.Key, &p.Position, &p.Visible)
if err != nil {
return nil, err
}
result = append(result, p)
}
if err := rows.Err(); err != nil {
return nil, err
}
return result, nil
}