feat: cache community metadata in wallet

Fixes #12521
This commit is contained in:
Dario Gabriel Lipicar 2023-10-26 03:30:18 -03:00 committed by dlipicar
parent dca38d1d32
commit a38b34ae49
16 changed files with 586 additions and 264 deletions

View File

@ -106,7 +106,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
// Community collectibles.
// Messenger needs the CollectiblesManager to get the list of collectibles owned
// by a certain account and check community entry permissions.
// We handle circular dependency between the two by delaying ininitalization of the CollectibleMetadataProvider
// We handle circular dependency between the two by delaying ininitalization of the CommunityCollectibleInfoProvider
// in the CollectiblesManager.
if config.WakuConfig.Enabled {
wakuService, err := b.wakuService(&config.WakuConfig, &config.ClusterConfig)
@ -125,7 +125,6 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
services = append(services, wakuext)
b.SetWalletCollectibleMetadataProvider(wakuext)
b.SetWalletCollectibleCommunityInfoProvider(wakuext)
}
@ -153,7 +152,6 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
services = append(services, wakuext)
b.SetWalletCollectibleMetadataProvider(wakuext)
b.SetWalletCollectibleCommunityInfoProvider(wakuext)
}
@ -514,12 +512,6 @@ func (b *StatusNode) WalletService() *wallet.Service {
return b.walletSrvc
}
func (b *StatusNode) SetWalletCollectibleMetadataProvider(provider thirdparty.CollectibleMetadataProvider) {
if b.walletSrvc != nil {
b.walletSrvc.SetCollectibleMetadataProvider(provider)
}
}
func (b *StatusNode) SetWalletCollectibleCommunityInfoProvider(provider thirdparty.CollectibleCommunityInfoProvider) {
if b.walletSrvc != nil {
b.walletSrvc.SetCollectibleCommunityInfoProvider(provider)

View File

@ -555,64 +555,80 @@ func tokenURIToCommunityID(tokenURI string) string {
return communityID
}
func (s *Service) CanProvideCollectibleMetadata(id thirdparty.CollectibleUniqueID, tokenURI string) (bool, error) {
ret := tokenURI != "" && tokenURIToCommunityID(tokenURI) != ""
return ret, nil
func (s *Service) GetCommunityID(tokenURI string) string {
if tokenURI != "" {
return tokenURIToCommunityID(tokenURI)
}
return ""
}
func (s *Service) FetchCollectibleMetadata(id thirdparty.CollectibleUniqueID, tokenURI string) (*thirdparty.FullCollectibleData, error) {
func (s *Service) FillCollectibleMetadata(collectible *thirdparty.FullCollectibleData) error {
if s.messenger == nil {
return nil, fmt.Errorf("messenger not ready")
return fmt.Errorf("messenger not ready")
}
communityID := tokenURIToCommunityID(tokenURI)
if collectible == nil {
return fmt.Errorf("empty collectible")
}
id := collectible.CollectibleData.ID
communityID := collectible.CollectibleData.CommunityID
if communityID == "" {
return nil, fmt.Errorf("invalid tokenURI")
return fmt.Errorf("invalid communityID")
}
community, err := s.fetchCommunity(communityID)
if err != nil {
return nil, err
return err
}
if community == nil {
return nil, nil
return nil
}
tokenMetadata, err := s.fetchCommunityCollectibleMetadata(community, id.ContractID)
if err != nil {
return nil, err
return err
}
if tokenMetadata == nil {
return nil, nil
return nil
}
token, err := s.fetchCommunityToken(communityID, id.ContractID)
communityToken, err := s.fetchCommunityToken(communityID, id.ContractID)
if err != nil {
return nil, err
return err
}
return &thirdparty.FullCollectibleData{
CollectibleData: thirdparty.CollectibleData{
ID: id,
CommunityID: communityID,
Name: tokenMetadata.GetName(),
Description: tokenMetadata.GetDescription(),
ImageURL: tokenMetadata.GetImage(),
TokenURI: tokenURI,
Traits: getCollectibleCommunityTraits(token),
},
CollectionData: &thirdparty.CollectionData{
permission := fetchCommunityCollectiblePermission(community, id)
privilegesLevel := token.CommunityLevel
if permission != nil {
privilegesLevel = permissionTypeToPrivilegesLevel(permission.GetType())
}
collectible.CollectibleData.Name = tokenMetadata.GetName()
collectible.CollectibleData.Description = tokenMetadata.GetDescription()
collectible.CollectibleData.ImageURL = tokenMetadata.GetImage()
collectible.CollectibleData.Traits = getCollectibleCommunityTraits(communityToken)
if collectible.CollectionData == nil {
collectible.CollectionData = &thirdparty.CollectionData{
ID: id.ContractID,
CommunityID: communityID,
Name: tokenMetadata.GetName(),
ImageURL: tokenMetadata.GetImage(),
},
}, nil
}
}
collectible.CollectionData.Name = tokenMetadata.GetName()
collectible.CollectionData.ImageURL = tokenMetadata.GetImage()
collectible.CommunityInfo = &thirdparty.CollectibleCommunityInfo{
PrivilegesLevel: privilegesLevel,
}
return nil
}
func permissionTypeToPrivilegesLevel(permissionType protobuf.CommunityTokenPermission_Type) token.PrivilegesLevel {
@ -626,7 +642,7 @@ func permissionTypeToPrivilegesLevel(permissionType protobuf.CommunityTokenPermi
}
}
func (s *Service) FetchCollectibleCommunityInfo(communityID string, id thirdparty.CollectibleUniqueID) (*thirdparty.CollectiblesCommunityInfo, error) {
func (s *Service) FetchCommunityInfo(communityID string) (*thirdparty.CommunityInfo, error) {
community, err := s.fetchCommunity(communityID)
if err != nil {
return nil, err
@ -635,37 +651,13 @@ func (s *Service) FetchCollectibleCommunityInfo(communityID string, id thirdpart
return nil, nil
}
metadata, err := s.fetchCommunityCollectibleMetadata(community, id.ContractID)
if err != nil {
return nil, err
}
if metadata == nil {
return nil, nil
communityInfo := &thirdparty.CommunityInfo{
CommunityName: community.Name(),
CommunityColor: community.Color(),
CommunityImage: fetchCommunityImage(community),
}
permission := fetchCommunityCollectiblePermission(community, id)
privilegesLevel := token.CommunityLevel
if permission != nil {
privilegesLevel = permissionTypeToPrivilegesLevel(permission.GetType())
}
return &thirdparty.CollectiblesCommunityInfo{
CommunityID: communityID,
CommunityName: community.Name(),
CommunityColor: community.Color(),
CommunityImage: fetchCommunityImage(community),
PrivilegesLevel: privilegesLevel,
}, nil
}
func (s *Service) FetchCollectibleCommunityTraits(communityID string, id thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleTrait, error) {
token, err := s.fetchCommunityToken(communityID, id.ContractID)
if err != nil {
return nil, err
}
return getCollectibleCommunityTraits(token), nil
return communityInfo, nil
}
func (s *Service) fetchCommunity(communityID string) (*communities.Community, error) {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"math/big"
"github.com/status-im/status-go/protocol/communities/token"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/sqlite"
@ -21,6 +22,7 @@ func NewCollectibleDataDB(sqlDb *sql.DB) *CollectibleDataDB {
}
const collectibleDataColumns = "chain_id, contract_address, token_id, provider, name, description, permalink, image_url, animation_url, animation_media_type, background_color, token_uri, community_id"
const collectibleCommunityDataColumns = "community_privileges_level"
const collectibleTraitsColumns = "chain_id, contract_address, token_id, trait_type, trait_value, display_type, max_value"
const selectCollectibleTraitsColumns = "trait_type, trait_value, display_type, max_value"
@ -252,3 +254,70 @@ func (o *CollectibleDataDB) GetData(ids []thirdparty.CollectibleUniqueID) (map[s
}
return ret, nil
}
func (o *CollectibleDataDB) SetCommunityInfo(id thirdparty.CollectibleUniqueID, communityInfo thirdparty.CollectibleCommunityInfo) (err error) {
tx, err := o.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
update, err := tx.Prepare(`UPDATE collectible_data_cache
SET community_privileges_level=?
WHERE chain_id=? AND contract_address=? AND token_id=?`)
if err != nil {
return err
}
_, err = update.Exec(
communityInfo.PrivilegesLevel,
id.ContractID.ChainID,
id.ContractID.Address,
(*bigint.SQLBigIntBytes)(id.TokenID.Int),
)
return err
}
func (o *CollectibleDataDB) GetCommunityInfo(id thirdparty.CollectibleUniqueID) (*thirdparty.CollectibleCommunityInfo, error) {
ret := thirdparty.CollectibleCommunityInfo{
PrivilegesLevel: token.CommunityLevel,
}
getData, err := o.db.Prepare(fmt.Sprintf(`SELECT %s
FROM collectible_data_cache
WHERE chain_id=? AND contract_address=? AND token_id=?`, collectibleCommunityDataColumns))
if err != nil {
return nil, err
}
row := getData.QueryRow(
id.ContractID.ChainID,
id.ContractID.Address,
(*bigint.SQLBigIntBytes)(id.TokenID.Int),
)
var dbPrivilegesLevel sql.NullByte
err = row.Scan(
&dbPrivilegesLevel,
)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
if dbPrivilegesLevel.Valid {
ret.PrivilegesLevel = token.PrivilegesLevel(dbPrivilegesLevel.Byte)
}
return &ret, nil
}

View File

@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/protocol/communities/token"
"github.com/status-im/status-go/services/wallet/bigint"
w_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty"
@ -72,6 +73,17 @@ func generateTestCollectiblesData(count int) (result []thirdparty.CollectibleDat
return result
}
func generateTestCommunityData(count int) []thirdparty.CollectibleCommunityInfo {
result := make([]thirdparty.CollectibleCommunityInfo, 0, count)
for i := 0; i < count; i++ {
newCommunityInfo := thirdparty.CollectibleCommunityInfo{
PrivilegesLevel: token.PrivilegesLevel(i),
}
result = append(result, newCommunityInfo)
}
return result
}
func TestUpdateCollectiblesData(t *testing.T) {
db, cleanDB := setupCollectibleDataDBTest(t)
defer cleanDB()
@ -146,3 +158,28 @@ func TestUpdateCollectiblesData(t *testing.T) {
require.Equal(t, c0, loadedMap[c0.ID.HashKey()])
require.Equal(t, c1, loadedMap[c1.ID.HashKey()])
}
func TestUpdateCommunityData(t *testing.T) {
db, cleanDB := setupCollectibleDataDBTest(t)
defer cleanDB()
const nData = 50
data := generateTestCollectiblesData(nData)
communityData := generateTestCommunityData(nData)
var err error
err = db.SetData(data)
require.NoError(t, err)
for i := 0; i < nData; i++ {
err = db.SetCommunityInfo(data[i].ID, communityData[i])
require.NoError(t, err)
}
for i := 0; i < nData; i++ {
loadedCommunityData, err := db.GetCommunityInfo(data[i].ID)
require.NoError(t, err)
require.Equal(t, communityData[i], *loadedCommunityData)
}
}

View File

@ -214,7 +214,7 @@ func (c *loadOwnedCollectiblesCommand) Run(parent context.Context) (err error) {
break
}
log.Debug("partial loadOwnedCollectiblesCommand", "chain", c.chainID, "account", c.account, "page", pageNr, "in", time.Since(pageStart), "found", len(partialOwnership.Items), "collectibles")
log.Debug("partial loadOwnedCollectiblesCommand", "chain", c.chainID, "account", c.account, "page", pageNr, "in", time.Since(pageStart), "found", len(partialOwnership.Items))
c.partialOwnership = append(c.partialOwnership, partialOwnership.Items...)

View File

@ -18,6 +18,7 @@ import (
"github.com/status-im/status-go/services/accounts/settingsevent"
"github.com/status-im/status-go/services/wallet/async"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/services/wallet/walletevent"
)
@ -35,6 +36,7 @@ type timerPerAddressAndChainID = map[common.Address]timerPerChainID
type Controller struct {
manager *Manager
ownershipDB *OwnershipDB
communityDB *community.DataDB
walletFeed *event.Feed
accountsDB *accounts.Database
accountsFeed *event.Feed
@ -64,6 +66,7 @@ func NewController(
return &Controller{
manager: manager,
ownershipDB: NewOwnershipDB(db),
communityDB: community.NewDataDB(db),
walletFeed: walletFeed,
accountsDB: accountsDB,
accountsFeed: accountsFeed,
@ -412,7 +415,8 @@ func (c *Controller) notifyCommunityCollectiblesReceived(ownedCollectibles Owned
continue
}
communityInfo, err := c.manager.FetchCollectibleCommunityInfo(communityID, collectibleID)
communityInfo, err := c.communityDB.GetCommunityInfo(communityID)
if err != nil {
log.Error("Error fetching community info", "error", err)
continue
@ -421,7 +425,7 @@ func (c *Controller) notifyCommunityCollectiblesReceived(ownedCollectibles Owned
header := CommunityCollectibleHeader{
ID: collectibleID,
Name: collectibleData.CollectibleData.Name,
CommunityHeader: communityInfoToHeader(*communityInfo),
CommunityHeader: communityInfoToHeader(communityID, communityInfo, collectibleData.CommunityInfo),
}
communityCollectibles = append(communityCollectibles, header)

View File

@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"math/big"
"net/http"
"strings"
@ -20,6 +19,7 @@ import (
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/connection"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/walletevent"
@ -54,13 +54,13 @@ type Manager struct {
collectibleDataProviders []thirdparty.CollectibleDataProvider
collectionDataProviders []thirdparty.CollectionDataProvider
collectibleProviders []thirdparty.CollectibleProvider
metadataProvider thirdparty.CollectibleMetadataProvider
communityInfoProvider thirdparty.CollectibleCommunityInfoProvider
httpClient *http.Client
collectiblesDataDB *CollectibleDataDB
collectionsDataDB *CollectionDataDB
communityDataDB *community.DataDB
statuses map[string]*connection.Status
statusNotifier *connection.StatusNotifier
@ -134,6 +134,7 @@ func NewManager(
},
collectiblesDataDB: NewCollectibleDataDB(db),
collectionsDataDB: NewCollectionDataDB(db),
communityDataDB: community.NewDataDB(db),
statuses: statuses,
statusNotifier: statusNotifier,
}
@ -196,10 +197,6 @@ func (o *Manager) doContentTypeRequest(url string) (string, error) {
}
// Used to break circular dependency, call once as soon as possible after initialization
func (o *Manager) SetMetadataProvider(metadataProvider thirdparty.CollectibleMetadataProvider) {
o.metadataProvider = metadataProvider
}
func (o *Manager) SetCommunityInfoProvider(communityInfoProvider thirdparty.CollectibleCommunityInfoProvider) {
o.communityInfoProvider = communityInfoProvider
}
@ -489,62 +486,72 @@ func (o *Manager) fetchTokenURI(id thirdparty.CollectibleUniqueID) (string, erro
}
func (o *Manager) processFullCollectibleData(assets []thirdparty.FullCollectibleData) error {
fullyFetchedAssets := make(map[string]*thirdparty.FullCollectibleData)
communityCollectibles := make(map[string][]*thirdparty.FullCollectibleData)
// Start with all assets, remove if any of the fetch steps fail
for idx := range assets {
asset := &assets[idx]
id := asset.CollectibleData.ID
fullyFetchedAssets[id.HashKey()] = asset
}
for _, asset := range fullyFetchedAssets {
// Only check community ownership if metadata is empty
if isMetadataEmpty(asset.CollectibleData) {
err := o.fillTokenURI(asset)
if err != nil {
log.Error("fillTokenURI failed", "err", err)
delete(fullyFetchedAssets, asset.CollectibleData.ID.HashKey())
continue
}
err = o.fillCommunityID(asset)
if err != nil {
log.Error("fillCommunityID failed", "err", err)
delete(fullyFetchedAssets, asset.CollectibleData.ID.HashKey())
continue
}
communityID := asset.CollectibleData.CommunityID
if communityID != "" {
if _, ok := communityCollectibles[communityID]; !ok {
communityCollectibles[communityID] = make([]*thirdparty.FullCollectibleData, 0)
}
communityCollectibles[communityID] = append(communityCollectibles[communityID], asset)
}
}
}
// Community collectibles are grouped by community ID
// If fetching data for one community fails (for example, owner node is down),
// skip and continue with the other communities.
for communityID, communityAssets := range communityCollectibles {
err := o.fillCommunityInfo(communityID, communityAssets)
if err != nil {
log.Error("fillCommunityInfo failed", "communityID", communityID, "err", err)
for _, communityAsset := range communityAssets {
delete(fullyFetchedAssets, communityAsset.CollectibleData.ID.HashKey())
}
}
}
for _, asset := range fullyFetchedAssets {
err := o.fillAnimationMediatype(asset)
if err != nil {
log.Error("fillAnimationMediatype failed", "err", err)
delete(fullyFetchedAssets, asset.CollectibleData.ID.HashKey())
continue
}
}
// Save successfully fetched data to DB
collectiblesData := make([]thirdparty.CollectibleData, 0, len(assets))
collectionsData := make([]thirdparty.CollectionData, 0, len(assets))
missingCollectionIDs := make([]thirdparty.ContractID, 0)
for _, asset := range assets {
for _, asset := range fullyFetchedAssets {
id := asset.CollectibleData.ID
// Get Metadata from alternate source if empty
if isMetadataEmpty(asset.CollectibleData) {
if o.metadataProvider == nil {
return fmt.Errorf("CollectibleMetadataProvider not available")
}
tokenURI := asset.CollectibleData.TokenURI
var err error
if tokenURI == "" {
tokenURI, err = o.fetchTokenURI(id)
if err != nil {
return err
}
asset.CollectibleData.TokenURI = tokenURI
}
canProvide, err := o.metadataProvider.CanProvideCollectibleMetadata(id, tokenURI)
if err != nil {
return err
}
if canProvide {
metadata, err := o.metadataProvider.FetchCollectibleMetadata(id, tokenURI)
if err != nil {
// Metadata is available but fetching failed.
// Ideally we would retry, but for now we just skip it.
log.Error("Failed to fetch collectible metadata", "err", err)
continue
}
if metadata != nil {
asset = *metadata
}
}
}
// Get Animation MediaType
if len(asset.CollectibleData.AnimationURL) > 0 {
contentType, err := o.doContentTypeRequest(asset.CollectibleData.AnimationURL)
if err != nil {
asset.CollectibleData.AnimationURL = ""
}
asset.CollectibleData.AnimationMediaType = contentType
}
collectiblesData = append(collectiblesData, asset.CollectibleData)
if asset.CollectionData != nil {
collectionsData = append(collectionsData, *asset.CollectionData)
@ -558,6 +565,15 @@ func (o *Manager) processFullCollectibleData(assets []thirdparty.FullCollectible
return err
}
for _, asset := range assets {
if asset.CommunityInfo != nil {
err = o.collectiblesDataDB.SetCommunityInfo(asset.CollectibleData.ID, *asset.CommunityInfo)
if err != nil {
return err
}
}
}
err = o.collectionsDataDB.SetData(collectionsData)
if err != nil {
return err
@ -574,6 +590,69 @@ func (o *Manager) processFullCollectibleData(assets []thirdparty.FullCollectible
return nil
}
func (o *Manager) fillTokenURI(asset *thirdparty.FullCollectibleData) error {
id := asset.CollectibleData.ID
tokenURI := asset.CollectibleData.TokenURI
// Only need to fetch it from contract if it was empty
if tokenURI == "" {
tokenURI, err := o.fetchTokenURI(id)
if err != nil {
return err
}
asset.CollectibleData.TokenURI = tokenURI
}
return nil
}
func (o *Manager) fillCommunityID(asset *thirdparty.FullCollectibleData) error {
tokenURI := asset.CollectibleData.TokenURI
communityID := ""
if tokenURI != "" {
communityID = o.communityInfoProvider.GetCommunityID(tokenURI)
}
asset.CollectibleData.CommunityID = communityID
return nil
}
func (o *Manager) fillCommunityInfo(communityID string, communityAssets []*thirdparty.FullCollectibleData) error {
communityInfo, err := o.communityInfoProvider.FetchCommunityInfo(communityID)
if err != nil {
return err
}
if communityInfo != nil {
err := o.communityDataDB.SetCommunityInfo(communityID, *communityInfo)
if err != nil {
return err
}
for _, communityAsset := range communityAssets {
err := o.communityInfoProvider.FillCollectibleMetadata(communityAsset)
if err != nil {
return err
}
}
}
return nil
}
func (o *Manager) fillAnimationMediatype(asset *thirdparty.FullCollectibleData) error {
if len(asset.CollectibleData.AnimationURL) > 0 {
contentType, err := o.doContentTypeRequest(asset.CollectibleData.AnimationURL)
if err != nil {
asset.CollectibleData.AnimationURL = ""
}
asset.CollectibleData.AnimationMediaType = contentType
}
return nil
}
func (o *Manager) processCollectionData(collections []thirdparty.CollectionData) error {
return o.collectionsDataDB.SetData(collections)
}
@ -623,43 +702,6 @@ func (o *Manager) getCacheFullCollectibleData(uniqueIDs []thirdparty.Collectible
return ret, nil
}
func (o *Manager) FetchCollectibleCommunityInfo(communityID string, id thirdparty.CollectibleUniqueID) (*thirdparty.CollectiblesCommunityInfo, error) {
if o.communityInfoProvider == nil {
return nil, fmt.Errorf("CollectibleCommunityInfoProvider not available")
}
return o.communityInfoProvider.FetchCollectibleCommunityInfo(communityID, id)
}
func (o *Manager) FetchCollectibleCommunityTraits(communityID string, id thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleTrait, error) {
if o.communityInfoProvider == nil {
return nil, fmt.Errorf("CollectibleCommunityInfoProvider not available")
}
traits, err := o.communityInfoProvider.FetchCollectibleCommunityTraits(communityID, id)
if err != nil {
return nil, err
}
collectibleIDs := []thirdparty.CollectibleUniqueID{id}
collectiblesData, err := o.collectiblesDataDB.GetData(collectibleIDs)
if err != nil {
return nil, err
}
if collectible, ok := collectiblesData[id.HashKey()]; ok {
collectible.Traits = traits
collectiblesData[id.HashKey()] = collectible
err = o.collectiblesDataDB.SetData(mapToList(collectiblesData))
if err != nil {
return nil, err
}
}
return traits, nil
}
// Reset connection status to trigger notifications
// on the next status update
func (o *Manager) ResetConnectionStatus() {

View File

@ -16,6 +16,7 @@ import (
"github.com/status-im/status-go/services/wallet/async"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/walletevent"
)
@ -47,6 +48,7 @@ type Service struct {
manager *Manager
controller *Controller
ownershipDB *OwnershipDB
communityDB *community.DataDB
walletFeed *event.Feed
scheduler *async.MultiClientScheduler
}
@ -63,6 +65,7 @@ func NewService(
manager: manager,
controller: NewController(db, walletFeed, accountsDB, accountsFeed, settingsFeed, networkManager, manager),
ownershipDB: NewOwnershipDB(db),
communityDB: community.NewDataDB(db),
walletFeed: walletFeed,
scheduler: async.NewMultiClientScheduler(),
}
@ -254,12 +257,12 @@ func (s *Service) fullCollectiblesDataToHeaders(data []thirdparty.FullCollectibl
header := fullCollectibleDataToHeader(c)
if c.CollectibleData.CommunityID != "" {
communityInfo, err := s.manager.FetchCollectibleCommunityInfo(c.CollectibleData.CommunityID, c.CollectibleData.ID)
communityInfo, err := s.communityDB.GetCommunityInfo(c.CollectibleData.CommunityID)
if err != nil {
return nil, err
}
communityHeader := communityInfoToHeader(*communityInfo)
communityHeader := communityInfoToHeader(c.CollectibleData.CommunityID, communityInfo, c.CommunityInfo)
header.CommunityHeader = &communityHeader
}
@ -276,18 +279,13 @@ func (s *Service) fullCollectiblesDataToDetails(data []thirdparty.FullCollectibl
details := fullCollectibleDataToDetails(c)
if c.CollectibleData.CommunityID != "" {
traits, err := s.manager.FetchCollectibleCommunityTraits(c.CollectibleData.CommunityID, c.CollectibleData.ID)
if err != nil {
return nil, err
}
details.Traits = traits
communityInfo, err := s.manager.FetchCollectibleCommunityInfo(c.CollectibleData.CommunityID, c.CollectibleData.ID)
communityInfo, err := s.communityDB.GetCommunityInfo(c.CollectibleData.CommunityID)
if err != nil {
return nil, err
}
details.CommunityInfo = communityInfo
communityDetails := communityInfoToDetails(c.CollectibleData.CommunityID, communityInfo, c.CommunityInfo)
details.CommunityInfo = &communityDetails
}
res = append(res, details)

View File

@ -7,18 +7,18 @@ import (
// Combined Collection+Collectible info, used to display a detailed view of a collectible
type CollectibleDetails struct {
ID thirdparty.CollectibleUniqueID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ImageURL string `json:"image_url"`
AnimationURL string `json:"animation_url"`
AnimationMediaType string `json:"animation_media_type"`
Traits []thirdparty.CollectibleTrait `json:"traits"`
BackgroundColor string `json:"background_color"`
CollectionName string `json:"collection_name"`
CollectionSlug string `json:"collection_slug"`
CollectionImageURL string `json:"collection_image_url"`
CommunityInfo *thirdparty.CollectiblesCommunityInfo `json:"community_info,omitempty"`
ID thirdparty.CollectibleUniqueID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ImageURL string `json:"image_url"`
AnimationURL string `json:"animation_url"`
AnimationMediaType string `json:"animation_media_type"`
Traits []thirdparty.CollectibleTrait `json:"traits"`
BackgroundColor string `json:"background_color"`
CollectionName string `json:"collection_name"`
CollectionSlug string `json:"collection_slug"`
CollectionImageURL string `json:"collection_image_url"`
CommunityInfo *CommunityDetails `json:"community_info,omitempty"`
}
// Combined Collection+Collectible info, used to display a basic view of a collectible in a list
@ -35,6 +35,14 @@ type CollectibleHeader struct {
CommunityHeader *CommunityHeader `json:"community_header,omitempty"`
}
type CommunityDetails struct {
CommunityID string `json:"community_id"`
CommunityName string `json:"community_name"`
CommunityColor string `json:"community_color"`
CommunityImage string `json:"community_image"`
PrivilegesLevel token.PrivilegesLevel `json:"privileges_level"`
}
type CommunityHeader struct {
CommunityID string `json:"community_id"`
CommunityName string `json:"community_name"`
@ -84,11 +92,37 @@ func fullCollectibleDataToDetails(c thirdparty.FullCollectibleData) CollectibleD
return ret
}
func communityInfoToHeader(c thirdparty.CollectiblesCommunityInfo) CommunityHeader {
return CommunityHeader{
CommunityID: c.CommunityID,
CommunityName: c.CommunityName,
CommunityColor: c.CommunityColor,
PrivilegesLevel: c.PrivilegesLevel,
func communityInfoToHeader(communityID string, community *thirdparty.CommunityInfo, communityCollectible *thirdparty.CollectibleCommunityInfo) CommunityHeader {
ret := CommunityHeader{
CommunityID: communityID,
}
if community != nil {
ret.CommunityName = community.CommunityName
ret.CommunityColor = community.CommunityColor
}
if communityCollectible != nil {
ret.PrivilegesLevel = communityCollectible.PrivilegesLevel
}
return ret
}
func communityInfoToDetails(communityID string, community *thirdparty.CommunityInfo, communityCollectible *thirdparty.CollectibleCommunityInfo) CommunityDetails {
ret := CommunityDetails{
CommunityID: communityID,
}
if community != nil {
ret.CommunityName = community.CommunityName
ret.CommunityColor = community.CommunityColor
ret.CommunityImage = community.CommunityImage
}
if communityCollectible != nil {
ret.PrivilegesLevel = communityCollectible.PrivilegesLevel
}
return ret
}

View File

@ -0,0 +1,77 @@
package community
import (
"database/sql"
"fmt"
"github.com/status-im/status-go/services/wallet/thirdparty"
)
type DataDB struct {
db *sql.DB
}
func NewDataDB(sqlDb *sql.DB) *DataDB {
return &DataDB{
db: sqlDb,
}
}
const communityDataColumns = "id, name, color, image"
const selectCommunityDataColumns = "name, color, image"
func (o *DataDB) SetCommunityInfo(id string, c thirdparty.CommunityInfo) (err error) {
tx, err := o.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
update, err := tx.Prepare(fmt.Sprintf(`INSERT OR REPLACE INTO community_data_cache (%s)
VALUES (?, ?, ?, ?)`, communityDataColumns))
if err != nil {
return err
}
_, err = update.Exec(
id,
c.CommunityName,
c.CommunityColor,
c.CommunityImage,
)
return err
}
func (o *DataDB) GetCommunityInfo(id string) (*thirdparty.CommunityInfo, error) {
var ret thirdparty.CommunityInfo
getData, err := o.db.Prepare(fmt.Sprintf(`SELECT %s
FROM community_data_cache
WHERE id=?`, selectCommunityDataColumns))
if err != nil {
return nil, err
}
row := getData.QueryRow(id)
err = row.Scan(
&ret.CommunityName,
&ret.CommunityColor,
&ret.CommunityImage,
)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &ret, nil
}

View File

@ -0,0 +1,52 @@
package community
import (
"fmt"
"testing"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
"github.com/stretchr/testify/require"
)
func setupCommunityDataDBTest(t *testing.T) (*DataDB, func()) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
return NewDataDB(db), func() {
require.NoError(t, db.Close())
}
}
func generateTestCommunityInfo(count int) map[string]thirdparty.CommunityInfo {
result := make(map[string]thirdparty.CommunityInfo)
for i := 0; i < count; i++ {
communityID := fmt.Sprintf("communityid-%d", i)
newCommunity := thirdparty.CommunityInfo{
CommunityName: fmt.Sprintf("communityname-%d", i),
CommunityColor: fmt.Sprintf("communitycolor-%d", i),
CommunityImage: fmt.Sprintf("communityimage-%d", i),
}
result[communityID] = newCommunity
}
return result
}
func TestUpdateCommunityInfo(t *testing.T) {
db, cleanup := setupCommunityDataDBTest(t)
defer cleanup()
communityData := generateTestCommunityInfo(10)
for communityID, communityInfo := range communityData {
err := db.SetCommunityInfo(communityID, communityInfo)
require.NoError(t, err)
}
for communityID, communityInfo := range communityData {
communityInfoFromDB, err := db.GetCommunityInfo(communityID)
require.NoError(t, err)
require.Equal(t, communityInfo, *communityInfoFromDB)
}
}

View File

@ -209,11 +209,6 @@ func (s *Service) Start() error {
return err
}
// Set external Collectibles metadata provider
func (s *Service) SetCollectibleMetadataProvider(provider thirdparty.CollectibleMetadataProvider) {
s.collectiblesManager.SetMetadataProvider(provider)
}
// Set external Collectibles community info provider
func (s *Service) SetCollectibleCommunityInfoProvider(provider thirdparty.CollectibleCommunityInfoProvider) {
s.collectiblesManager.SetCommunityInfoProvider(provider)

View File

@ -134,11 +134,17 @@ type CollectibleData struct {
TokenURI string `json:"token_uri"`
}
// Community-related collectible info. Present only for collectibles minted in a community.
type CollectibleCommunityInfo struct {
PrivilegesLevel token.PrivilegesLevel `json:"privileges_level"`
}
// Combined Collection+Collectible info returned by the CollectibleProvider
// Some providers may not return the CollectionData in the same API call, so it's optional
type FullCollectibleData struct {
CollectibleData CollectibleData
CollectionData *CollectionData
CommunityInfo *CollectibleCommunityInfo
}
type CollectiblesContainer[T any] struct {
@ -171,28 +177,6 @@ func (c *FullCollectibleDataContainer) ToOwnershipContainer() CollectibleOwnersh
}
}
// Community-related info. Present only for collectibles minted in a community.
// This info is directly fetched every time upon request since a change in community
// settings could affect it.
type CollectiblesCommunityInfo struct {
CommunityID string `json:"community_id"`
CommunityName string `json:"community_name"`
CommunityColor string `json:"community_color"`
CommunityImage string `json:"community_image"`
PrivilegesLevel token.PrivilegesLevel `json:"privileges_level"`
}
type CollectibleMetadataProvider interface {
CanProvideCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (bool, error)
FetchCollectibleMetadata(id CollectibleUniqueID, tokenURI string) (*FullCollectibleData, error)
}
type CollectibleCommunityInfoProvider interface {
FetchCollectibleCommunityInfo(communityID string, id CollectibleUniqueID) (*CollectiblesCommunityInfo, error)
FetchCollectibleCommunityTraits(communityID string, id CollectibleUniqueID) ([]CollectibleTrait, error)
}
type TokenBalance struct {
TokenID *bigint.BigInt `json:"tokenId"`
Balance *bigint.BigInt `json:"balance"`
@ -230,3 +214,8 @@ type CollectionDataProvider interface {
CollectibleProvider
FetchCollectionsDataByContractID(ids []ContractID) ([]CollectionData, error)
}
type CollectibleCommunityInfoProvider interface {
CommunityInfoProvider
FillCollectibleMetadata(collectible *FullCollectibleData) error
}

View File

@ -0,0 +1,13 @@
package thirdparty
// Community-related info used by the wallet, cached in the wallet db.
type CommunityInfo struct {
CommunityName string `json:"community_name"`
CommunityColor string `json:"community_color"`
CommunityImage string `json:"community_image"`
}
type CommunityInfoProvider interface {
GetCommunityID(tokenURI string) string
FetchCommunityInfo(communityID string) (*CommunityInfo, error)
}

View File

@ -9,6 +9,7 @@
// 1695932536_balance_history_v2.up.sql (653B)
// 1696853635_input_data.up.sql (23.14kB)
// 1698117918_add_community_id_to_tokens.up.sql (61B)
// 1698257443_add_community_metadata_to_wallet_db.up.sql (323B)
// doc.go (74B)
package migrations
@ -19,7 +20,6 @@ import (
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -29,7 +29,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
var buf bytes.Buffer
@ -37,7 +37,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("read %q: %v", name, err)
return nil, fmt.Errorf("read %q: %w", name, err)
}
if clErr != nil {
return nil, err
@ -93,7 +93,7 @@ func _1691753758_initialUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1691753758_initial.up.sql", size: 5738, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1691753758_initial.up.sql", size: 5738, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x6b, 0x25, 0x31, 0xc8, 0x27, 0x3, 0x6b, 0x9f, 0x15, 0x42, 0x2f, 0x85, 0xfb, 0xe3, 0x6, 0xea, 0xf7, 0x97, 0x12, 0x56, 0x3c, 0x9a, 0x5b, 0x1a, 0xca, 0xb1, 0x23, 0xfa, 0xcd, 0x57, 0x25, 0x5c}}
return a, nil
}
@ -113,7 +113,7 @@ func _1692701329_add_collectibles_and_collections_data_cacheUpSql() (*asset, err
return nil, err
}
info := bindataFileInfo{name: "1692701329_add_collectibles_and_collections_data_cache.up.sql", size: 1808, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1692701329_add_collectibles_and_collections_data_cache.up.sql", size: 1808, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0x51, 0xf4, 0x2b, 0x92, 0xde, 0x59, 0x65, 0xd8, 0x9b, 0x57, 0xe0, 0xfd, 0x7b, 0x12, 0xb, 0x29, 0x6e, 0x9d, 0xb5, 0x90, 0xe, 0xfa, 0x12, 0x97, 0xd, 0x61, 0x60, 0x7f, 0x32, 0x1d, 0xc3}}
return a, nil
}
@ -133,7 +133,7 @@ func _1692701339_add_scope_to_pendingUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1692701339_add_scope_to_pending.up.sql", size: 576, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1692701339_add_scope_to_pending.up.sql", size: 576, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x36, 0x8a, 0x5e, 0xe2, 0x63, 0x15, 0x37, 0xba, 0x55, 0x18, 0xf3, 0xcc, 0xe0, 0x5, 0x84, 0xe1, 0x5b, 0xe8, 0x1, 0x32, 0x6b, 0x9f, 0x7d, 0x9f, 0xd9, 0x23, 0x6c, 0xa9, 0xb5, 0xdc, 0xf4, 0x93}}
return a, nil
}
@ -153,7 +153,7 @@ func _1694540071_add_collectibles_ownership_update_timestampUpSql() (*asset, err
return nil, err
}
info := bindataFileInfo{name: "1694540071_add_collectibles_ownership_update_timestamp.up.sql", size: 349, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1694540071_add_collectibles_ownership_update_timestamp.up.sql", size: 349, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7f, 0x45, 0xc7, 0xce, 0x79, 0x63, 0xbc, 0x6f, 0x83, 0x5f, 0xe2, 0x3, 0x56, 0xcc, 0x5, 0x2f, 0x85, 0xda, 0x7e, 0xea, 0xf5, 0xd2, 0xac, 0x19, 0xd4, 0xd8, 0x5e, 0xdd, 0xed, 0xe2, 0xa9, 0x97}}
return a, nil
}
@ -173,7 +173,7 @@ func _1694692748_add_raw_balance_to_token_balancesUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1694692748_add_raw_balance_to_token_balances.up.sql", size: 165, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1694692748_add_raw_balance_to_token_balances.up.sql", size: 165, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd4, 0xe0, 0x5b, 0x42, 0xf0, 0x96, 0xa5, 0xf5, 0xed, 0xc0, 0x97, 0x88, 0xb0, 0x6d, 0xfe, 0x7d, 0x97, 0x2e, 0x17, 0xd2, 0x16, 0xbc, 0x2a, 0xf2, 0xcc, 0x67, 0x9e, 0xc5, 0x47, 0xf6, 0x69, 0x1}}
return a, nil
}
@ -193,7 +193,7 @@ func _1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSq
return nil, err
}
info := bindataFileInfo{name: "1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql", size: 275, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql", size: 275, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xfa, 0x2, 0xa, 0x7f, 0x4b, 0xd1, 0x3, 0xd0, 0x3, 0x29, 0x84, 0x31, 0xed, 0x49, 0x4f, 0xb1, 0x2d, 0xd7, 0x80, 0x41, 0x5b, 0xfa, 0x6, 0xae, 0xb4, 0xf6, 0x6b, 0x49, 0xee, 0x57, 0x33, 0x76}}
return a, nil
}
@ -213,7 +213,7 @@ func _1695932536_balance_history_v2UpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1695932536_balance_history_v2.up.sql", size: 653, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1695932536_balance_history_v2.up.sql", size: 653, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x37, 0xf4, 0x14, 0x91, 0xf6, 0x5f, 0xc4, 0x9b, 0xb7, 0x83, 0x32, 0x72, 0xbe, 0x82, 0x42, 0x39, 0xa4, 0x3b, 0xc9, 0x78, 0x3d, 0xca, 0xd4, 0xbf, 0xfc, 0x7a, 0x33, 0x1e, 0xcd, 0x9e, 0xe4, 0x85}}
return a, nil
}
@ -233,7 +233,7 @@ func _1696853635_input_dataUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1696853635_input_data.up.sql", size: 23140, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "1696853635_input_data.up.sql", size: 23140, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x89, 0x30, 0x33, 0x33, 0x55, 0xc5, 0x57, 0x2b, 0xaf, 0xef, 0x3d, 0x8d, 0x2a, 0xaa, 0x5c, 0x32, 0xd1, 0xf4, 0xd, 0x4a, 0xd0, 0x33, 0x4a, 0xe8, 0xf6, 0x8, 0x6b, 0x65, 0xcc, 0xba, 0xed, 0x42}}
return a, nil
}
@ -253,11 +253,31 @@ func _1698117918_add_community_id_to_tokensUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1698117918_add_community_id_to_tokens.up.sql", size: 61, mode: os.FileMode(0644), modTime: time.Unix(1698290254, 0)}
info := bindataFileInfo{name: "1698117918_add_community_id_to_tokens.up.sql", size: 61, mode: os.FileMode(0644), modTime: time.Unix(1698257400, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb3, 0x82, 0xdb, 0xde, 0x3, 0x3, 0xc, 0x67, 0xf3, 0x54, 0xc4, 0xad, 0xd6, 0xce, 0x56, 0xfb, 0xc1, 0x87, 0xd7, 0xda, 0xab, 0xec, 0x1, 0xe1, 0x7d, 0xb3, 0x63, 0xd6, 0xe5, 0x5d, 0x1c, 0x15}}
return a, nil
}
var __1698257443_add_community_metadata_to_wallet_dbUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8e\xc1\x4e\xc3\x30\x10\x44\xef\xfe\x8a\x39\x82\x44\xbe\xa0\xa7\xd0\x18\x88\x48\x1d\x94\x38\x52\x7b\xb2\x8c\xbd\x14\x4b\xeb\x38\x6a\xdc\x4a\xf9\x7b\x44\xa9\x44\x41\xbd\xee\xce\x9b\x37\x45\x81\x76\xe4\x05\x53\x9a\x8e\x6c\x33\x79\x84\x0f\xb8\x14\xe3\x71\xcc\x8b\x09\x1e\x61\xc6\x98\x32\x28\x4e\x79\x11\x65\xa3\x65\x07\x5d\x3e\x36\x12\x2e\x31\x93\xcb\xe1\x9d\xc9\x78\x9b\xad\x71\xd6\x7d\x12\xca\xaa\xc2\xba\x6d\x86\x8d\xba\xd4\x84\xbc\x98\xe9\x10\x4e\x81\x69\x4f\xb3\x61\x3a\x11\x63\x50\x7d\xfd\xac\x64\x85\x5a\xe9\x95\x10\x45\x81\x97\xc4\x7e\xfe\x45\x80\x48\xd9\x7e\xf7\x8a\x75\x27\x4b\x2d\x2f\xd6\xfa\x09\xaa\xd5\x90\xdb\xba\xd7\xfd\x95\xe1\x6a\xc1\x9d\x00\x80\xe0\xa1\xe5\x56\xe3\xad\xab\x37\x65\xb7\xc3\xab\xdc\x9d\x49\x35\x34\xcd\xc3\x39\x31\xda\x48\x3f\x99\xbf\x77\x97\x38\x1d\x6e\x3d\x42\xb4\xfb\x7f\x84\xb8\x5f\x89\xaf\x00\x00\x00\xff\xff\x54\x75\x5f\xc6\x43\x01\x00\x00")
func _1698257443_add_community_metadata_to_wallet_dbUpSqlBytes() ([]byte, error) {
return bindataRead(
__1698257443_add_community_metadata_to_wallet_dbUpSql,
"1698257443_add_community_metadata_to_wallet_db.up.sql",
)
}
func _1698257443_add_community_metadata_to_wallet_dbUpSql() (*asset, error) {
bytes, err := _1698257443_add_community_metadata_to_wallet_dbUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1698257443_add_community_metadata_to_wallet_db.up.sql", size: 323, mode: os.FileMode(0644), modTime: time.Unix(1698301583, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x22, 0xd3, 0x4, 0x25, 0xfa, 0x23, 0x1, 0x48, 0x83, 0x26, 0x20, 0xf2, 0x3d, 0xbc, 0xc1, 0xa7, 0x7c, 0x27, 0x7c, 0x1d, 0x63, 0x3, 0xa, 0xd0, 0xce, 0x47, 0x86, 0xdc, 0xa1, 0x3c, 0x2, 0x1c}}
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) {
@ -273,7 +293,7 @@ func docGo() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1697447579, 0)}
info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(0644), modTime: time.Unix(1698249648, 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
}
@ -369,36 +389,33 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"1691753758_initial.up.sql": _1691753758_initialUpSql,
"1692701329_add_collectibles_and_collections_data_cache.up.sql": _1692701329_add_collectibles_and_collections_data_cacheUpSql,
"1692701339_add_scope_to_pending.up.sql": _1692701339_add_scope_to_pendingUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": _1694540071_add_collectibles_ownership_update_timestampUpSql,
"1694692748_add_raw_balance_to_token_balances.up.sql": _1694692748_add_raw_balance_to_token_balancesUpSql,
"1691753758_initial.up.sql": _1691753758_initialUpSql,
"1692701329_add_collectibles_and_collections_data_cache.up.sql": _1692701329_add_collectibles_and_collections_data_cacheUpSql,
"1692701339_add_scope_to_pending.up.sql": _1692701339_add_scope_to_pendingUpSql,
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": _1694540071_add_collectibles_ownership_update_timestampUpSql,
"1694692748_add_raw_balance_to_token_balances.up.sql": _1694692748_add_raw_balance_to_token_balancesUpSql,
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": _1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql,
"1695932536_balance_history_v2.up.sql": _1695932536_balance_history_v2UpSql,
"1696853635_input_data.up.sql": _1696853635_input_dataUpSql,
"1698117918_add_community_id_to_tokens.up.sql": _1698117918_add_community_id_to_tokensUpSql,
"1695932536_balance_history_v2.up.sql": _1695932536_balance_history_v2UpSql,
"1696853635_input_data.up.sql": _1696853635_input_dataUpSql,
"1698117918_add_community_id_to_tokens.up.sql": _1698117918_add_community_id_to_tokensUpSql,
"1698257443_add_community_metadata_to_wallet_db.up.sql": _1698257443_add_community_metadata_to_wallet_dbUpSql,
"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
//
// 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
@ -431,16 +448,17 @@ type bintree struct {
}
var _bintree = &bintree{nil, map[string]*bintree{
"1691753758_initial.up.sql": &bintree{_1691753758_initialUpSql, map[string]*bintree{}},
"1692701329_add_collectibles_and_collections_data_cache.up.sql": &bintree{_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1692701339_add_scope_to_pending.up.sql": &bintree{_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}},
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": &bintree{_1694540071_add_collectibles_ownership_update_timestampUpSql, map[string]*bintree{}},
"1694692748_add_raw_balance_to_token_balances.up.sql": &bintree{_1694692748_add_raw_balance_to_token_balancesUpSql, map[string]*bintree{}},
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": &bintree{_1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1695932536_balance_history_v2.up.sql": &bintree{_1695932536_balance_history_v2UpSql, map[string]*bintree{}},
"1696853635_input_data.up.sql": &bintree{_1696853635_input_dataUpSql, map[string]*bintree{}},
"1698117918_add_community_id_to_tokens.up.sql": &bintree{_1698117918_add_community_id_to_tokensUpSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}},
"1691753758_initial.up.sql": {_1691753758_initialUpSql, map[string]*bintree{}},
"1692701329_add_collectibles_and_collections_data_cache.up.sql": {_1692701329_add_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1692701339_add_scope_to_pending.up.sql": {_1692701339_add_scope_to_pendingUpSql, map[string]*bintree{}},
"1694540071_add_collectibles_ownership_update_timestamp.up.sql": {_1694540071_add_collectibles_ownership_update_timestampUpSql, map[string]*bintree{}},
"1694692748_add_raw_balance_to_token_balances.up.sql": {_1694692748_add_raw_balance_to_token_balancesUpSql, map[string]*bintree{}},
"1695133989_add_community_id_to_collectibles_and_collections_data_cache.up.sql": {_1695133989_add_community_id_to_collectibles_and_collections_data_cacheUpSql, map[string]*bintree{}},
"1695932536_balance_history_v2.up.sql": {_1695932536_balance_history_v2UpSql, map[string]*bintree{}},
"1696853635_input_data.up.sql": {_1696853635_input_dataUpSql, map[string]*bintree{}},
"1698117918_add_community_id_to_tokens.up.sql": {_1698117918_add_community_id_to_tokensUpSql, map[string]*bintree{}},
"1698257443_add_community_metadata_to_wallet_db.up.sql": {_1698257443_add_community_metadata_to_wallet_dbUpSql, map[string]*bintree{}},
"doc.go": {docGo, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory.
@ -457,7 +475,7 @@ func RestoreAsset(dir, name string) error {
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
err = os.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}

View File

@ -0,0 +1,10 @@
-- Only populated if communty_id is not empty
ALTER TABLE collectible_data_cache ADD COLUMN community_privileges_level UNSIGNED INT;
-- Holds community metadata
CREATE TABLE IF NOT EXISTS community_data_cache (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
color TEXT NOT NULL,
image TEXT NOT NULL
);