Skip to content

Commit

Permalink
feat(api)_: add api and functionality to get collection website and t…
Browse files Browse the repository at this point in the history
…witter handle from alchemy
  • Loading branch information
Khushboo-dev-cpp committed May 13, 2024
1 parent 8f50b57 commit cc6867b
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 137 deletions.
9 changes: 9 additions & 0 deletions services/wallet/activity/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ func (m *mockCollectiblesManager) FetchAssetsByCollectibleUniqueID(ctx context.C
return res.([]thirdparty.FullCollectibleData), args.Error(1)
}

func (m *mockCollectiblesManager) FetchCollectionSocialsAsync(contractID thirdparty.ContractID) error {
args := m.Called(contractID)
res := args.Get(0)
if res == nil {
return args.Error(1)
}
return nil
}

// mockTokenManager implements the token.ManagerInterface
type mockTokenManager struct {
mock.Mock
Expand Down
6 changes: 6 additions & 0 deletions services/wallet/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,12 @@ func (api *API) GetCollectiblesByUniqueIDAsync(requestID int32, uniqueIDs []thir
return nil
}

func (api *API) FetchCollectionSocialsAsync(contractID thirdparty.ContractID) error {
log.Debug("wallet.api.FetchCollectionSocialsAsync", "contractID", contractID)

return api.s.collectiblesManager.FetchCollectionSocialsAsync(contractID)
}

func (api *API) GetCollectibleOwnersByContractAddress(ctx context.Context, chainID wcommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
log.Debug("call to GetCollectibleOwnersByContractAddress")
return api.s.collectiblesManager.FetchCollectibleOwnersByContractAddress(ctx, chainID, contractAddress)
Expand Down
108 changes: 108 additions & 0 deletions services/wallet/collectibles/collection_data_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ func NewCollectionDataDB(sqlDb *sql.DB) *CollectionDataDB {
const collectionDataColumns = "chain_id, contract_address, provider, name, slug, image_url, image_payload, community_id"
const collectionTraitsColumns = "chain_id, contract_address, trait_type, min, max"
const selectCollectionTraitsColumns = "trait_type, min, max"
const collectionSocialsColumns = "chain_id, contract_address, provider, website, twitter_handle"
const selectCollectionSocialsColumns = "website, twitter_handle, provider"

func rowsToCollectionTraits(rows *sql.Rows) (map[string]thirdparty.CollectionTrait, error) {
traits := make(map[string]thirdparty.CollectionTrait)
Expand Down Expand Up @@ -130,6 +132,13 @@ func setCollectionsData(creator sqlite.StatementCreator, collections []thirdpart
if err != nil {
return err
}

if c.Socials != nil {
err = upsertCollectionSocials(creator, c.ID, c.Socials)
if err != nil {
return err
}
}
}
}

Expand Down Expand Up @@ -246,8 +255,107 @@ func (o *CollectionDataDB) GetData(ids []thirdparty.ContractID) (map[string]thir
return nil, err
}

// Get socials from different table
c.Socials, err = getCollectionSocials(o.db, c.ID)
if err != nil {
return nil, err
}

ret[c.ID.HashKey()] = *c
}
}
return ret, nil
}

func (o *CollectionDataDB) GetSocialsForID(contractID thirdparty.ContractID) (*thirdparty.CollectionSocials, error) {
return getCollectionSocials(o.db, contractID)
}

func (o *CollectionDataDB) SetCollectionSocialsData(id thirdparty.ContractID, collectionSocials *thirdparty.CollectionSocials) (err error) {
tx, err := o.db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()

// Insert new collections socials
if collectionSocials != nil {
err = upsertCollectionSocials(tx, id, collectionSocials)
if err != nil {
return err
}
}

return
}

func rowsToCollectionSocials(rows *sql.Rows) (*thirdparty.CollectionSocials, error) {
var socials *thirdparty.CollectionSocials
socials = nil
for rows.Next() {
var website string
var twitterHandle string
var provider string
err := rows.Scan(
&website,
&twitterHandle,
&provider,
)
if err != nil {
return nil, err
}
socials = &thirdparty.CollectionSocials{
Website: website,
TwitterHandle: twitterHandle,
Provider: provider}
}
return socials, nil
}

func getCollectionSocials(creator sqlite.StatementCreator, id thirdparty.ContractID) (*thirdparty.CollectionSocials, error) {
// Get socials
selectSocials, err := creator.Prepare(fmt.Sprintf(`SELECT %s
FROM collection_socials_cache
WHERE chain_id = ? AND contract_address = ?`, selectCollectionSocialsColumns))
if err != nil {
return nil, err
}

rows, err := selectSocials.Query(
id.ChainID,
id.Address,
)
if err != nil {
return nil, err
}

return rowsToCollectionSocials(rows)
}

func upsertCollectionSocials(creator sqlite.StatementCreator, id thirdparty.ContractID, socials *thirdparty.CollectionSocials) error {
// Insert socials
insertSocial, err := creator.Prepare(fmt.Sprintf(`INSERT OR REPLACE INTO collection_socials_cache (%s)
VALUES (?, ?, ?, ?, ?)`, collectionSocialsColumns))
if err != nil {
return err
}

_, err = insertSocial.Exec(
id.ChainID,
id.Address,
socials.Provider,
socials.Website,
socials.TwitterHandle,
)
if err != nil {
return err
}

return nil
}
47 changes: 47 additions & 0 deletions services/wallet/collectibles/collection_data_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,50 @@ func TestUpdateCollectionsData(t *testing.T) {
require.Equal(t, c0, loadedMap[c0.ID.HashKey()])
require.Equal(t, c1, loadedMap[c1.ID.HashKey()])
}

func TestCollectionSocialsData(t *testing.T) {
db, cleanDB := setupCollectionDataDBTest(t)
defer cleanDB()

data := thirdparty.GenerateTestCollectionsData(10)

ids := make([]thirdparty.ContractID, 0, len(data))
for _, collection := range data {
ids = append(ids, collection.ID)
}

err := db.SetData(data, true)
require.NoError(t, err)

// Check for loaded data
loadedMap, err := db.GetData(ids)
require.NoError(t, err)
require.Equal(t, len(data), len(loadedMap))

// Valid check for ID should return false as it was not set initially
socials, err := db.GetSocialsForID(data[0].ID)
require.NoError(t, err)
require.Nil(t, socials)

// Now we'll try to set socials data for the first item
socialsToSet := &thirdparty.CollectionSocials{
Website: "new-website",
TwitterHandle: "newTwitterHandle",
Provider: "alchemy",
}
err = db.SetCollectionSocialsData(data[0].ID, socialsToSet)
require.NoError(t, err)

// Valid check for ID should return true as it was now set
socials, err = db.GetSocialsForID(data[0].ID)
require.NoError(t, err)
require.Equal(t, socials, socialsToSet)

// Check the loaded data again for socials
loadedMap, err = db.GetData(ids)
require.NoError(t, err)
require.Equal(t, len(data), len(loadedMap))

require.Equal(t, socials.Website, loadedMap[data[0].ID.HashKey()].Socials.Website)
require.Equal(t, socials.TwitterHandle, loadedMap[data[0].ID.HashKey()].Socials.TwitterHandle)
}
80 changes: 80 additions & 0 deletions services/wallet/collectibles/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var (

type ManagerInterface interface {
FetchAssetsByCollectibleUniqueID(ctx context.Context, uniqueIDs []thirdparty.CollectibleUniqueID, asyncFetch bool) ([]thirdparty.FullCollectibleData, error)
FetchCollectionSocialsAsync(contractID thirdparty.ContractID) error
}

type Manager struct {
Expand Down Expand Up @@ -1027,3 +1028,82 @@ func (o *Manager) SearchCollections(ctx context.Context, chainID walletCommon.Ch
}
return nil, ErrNoProvidersAvailableForChainID
}

func (o *Manager) FetchCollectionSocialsAsync(contractID thirdparty.ContractID) error {
go func() {
defer o.checkConnectionStatus(contractID.ChainID)

socials, err := o.getOrFetchSocialsForCollection(context.Background(), contractID)
if err != nil || socials == nil {
log.Debug("FetchCollectionSocialsAsync failed for", "chainID", contractID.ChainID, "address", contractID.Address, "err", err)
return
}

socialsMessage := CollectionSocialsMessage{
ID: contractID,
Socials: socials,
}

err = o.collectionsDataDB.SetCollectionSocialsData(contractID, socials)
if err != nil {
log.Error("Error saving socials to DB: %v", err)
return
}

payload, err := json.Marshal(socialsMessage)
if err != nil {
log.Error("Error marshaling response: %v", err)
return
}

event := walletevent.Event{
Type: EventGetCollectionSocialsDone,
Message: string(payload),
}

o.feed.Send(event)
}()

return nil
}

func (o *Manager) getOrFetchSocialsForCollection(ctx context.Context, contractID thirdparty.ContractID) (*thirdparty.CollectionSocials, error) {
socials, err := o.collectionsDataDB.GetSocialsForID(contractID)
if err != nil {
log.Debug("getOrFetchSocialsForCollection failed for", "chainID", contractID.ChainID, "address", contractID.Address, "err", err)
return nil, err
}
if socials == nil {
return o.fetchSocialsForCollection(context.Background(), contractID)
}
return socials, nil
}

func (o *Manager) fetchSocialsForCollection(ctx context.Context, contractID thirdparty.ContractID) (*thirdparty.CollectionSocials, error) {
cmd := circuitbreaker.Command{}
for _, provider := range o.providers.CollectibleDataProviders {
if !provider.IsChainSupported(contractID.ChainID) {
continue
}

provider := provider
cmd.Add(circuitbreaker.NewFunctor(func() ([]interface{}, error) {
socials, err := provider.FetchCollectionSocials(ctx, contractID)
if err != nil {
log.Error("FetchCollectionSocials failed for", "provider", provider.ID(), "chainID", contractID.ChainID, "err", err)
}
return []interface{}{socials}, err
}))
}

if cmd.IsEmpty() {
return nil, ErrNoProvidersAvailableForChainID // lets not stop the group if no providers are available for the chain
}

cmdRes := o.getCircuitBreaker(contractID.ChainID).Execute(cmd)
if cmdRes.Error() != nil {
log.Error("fetchSocialsForCollection failed for", "chainID", contractID.ChainID, "err", cmdRes.Error())
return nil, cmdRes.Error()
}
return cmdRes.Result()[0].(*thirdparty.CollectionSocials), cmdRes.Error()
}
6 changes: 6 additions & 0 deletions services/wallet/collectibles/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (

EventOwnedCollectiblesFilteringDone walletevent.EventType = "wallet-owned-collectibles-filtering-done"
EventGetCollectiblesDetailsDone walletevent.EventType = "wallet-get-collectibles-details-done"
EventGetCollectionSocialsDone walletevent.EventType = "wallet-get-collection-socials-done"
)

type OwnershipUpdateMessage struct {
Expand All @@ -43,6 +44,11 @@ type OwnershipUpdateMessage struct {
Removed []thirdparty.CollectibleUniqueID `json:"removed"`
}

type CollectionSocialsMessage struct {
ID thirdparty.ContractID `json:"id"`
Socials *thirdparty.CollectionSocials `json:"socials"`
}

type CollectibleDataType byte

const (
Expand Down
48 changes: 31 additions & 17 deletions services/wallet/collectibles/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ type CollectibleData struct {
}

type CollectionData struct {
Name string `json:"name"`
Slug string `json:"slug"`
ImageURL string `json:"image_url"`
Name string `json:"name"`
Slug string `json:"slug"`
ImageURL string `json:"image_url"`
Socials *CollectionSocials `json:"socials"`
}

type CollectionSocials struct {
Website string `json:"website"`
TwitterHandle string `json:"twitter_handle"`
}

type CommunityData struct {
Expand Down Expand Up @@ -64,6 +70,24 @@ func idsToCollectibles(ids []thirdparty.CollectibleUniqueID) []Collectible {
return res
}

func thirdpartyCollectionDataToCollectionData(collectionData *thirdparty.CollectionData) CollectionData {
ret := CollectionData{}
if collectionData != nil {
ret = CollectionData{
Name: collectionData.Name,
Slug: collectionData.Slug,
ImageURL: collectionData.ImageURL,
}
if collectionData.Socials != nil {
ret.Socials = &CollectionSocials{
Website: collectionData.Socials.Website,
TwitterHandle: collectionData.Socials.TwitterHandle,
}
}
}
return ret
}

func getContractType(c thirdparty.FullCollectibleData) w_common.ContractType {
if c.CollectibleData.ContractType != w_common.ContractTypeUnknown {
return c.CollectibleData.ContractType
Expand All @@ -88,13 +112,8 @@ func fullCollectibleDataToHeader(c thirdparty.FullCollectibleData) Collectible {
Soulbound: &c.CollectibleData.Soulbound,
},
}
if c.CollectionData != nil {
ret.CollectionData = &CollectionData{
Name: c.CollectionData.Name,
Slug: c.CollectionData.Slug,
ImageURL: c.CollectionData.ImageURL,
}
}
collectionData := thirdpartyCollectionDataToCollectionData(c.CollectionData)
ret.CollectionData = &collectionData
if c.CollectibleData.CommunityID != "" {
communityData := communityInfoToData(c.CollectibleData.CommunityID, c.CommunityInfo, c.CollectibleCommunityInfo)
ret.CommunityData = &communityData
Expand Down Expand Up @@ -130,13 +149,8 @@ func fullCollectibleDataToDetails(c thirdparty.FullCollectibleData) Collectible
Soulbound: &c.CollectibleData.Soulbound,
},
}
if c.CollectionData != nil {
ret.CollectionData = &CollectionData{
Name: c.CollectionData.Name,
Slug: c.CollectionData.Slug,
ImageURL: c.CollectionData.ImageURL,
}
}
collectionData := thirdpartyCollectionDataToCollectionData(c.CollectionData)
ret.CollectionData = &collectionData
if c.CollectibleData.CommunityID != "" {
communityData := communityInfoToData(c.CollectibleData.CommunityID, c.CommunityInfo, c.CollectibleCommunityInfo)
ret.CommunityData = &communityData
Expand Down

0 comments on commit cc6867b

Please sign in to comment.