From 2c55b9c676d71f75dfd07e0973e168c88cc93954 Mon Sep 17 00:00:00 2001 From: Patryk Osmaczko Date: Tue, 14 Nov 2023 22:06:33 +0100 Subject: [PATCH] feat: cache curated communities in db closes: status-im/status-desktop#12277 --- protocol/communities/manager.go | 44 +++-- protocol/communities/persistence.go | 71 +++++++- protocol/communities/persistence_test.go | 32 ++++ protocol/messenger_curated_communities.go | 159 ++++++++---------- protocol/migrations/migrations.go | 47 ++++-- .../1700044187_curated_communities.up.sql | 4 + 6 files changed, 236 insertions(+), 121 deletions(-) create mode 100644 protocol/migrations/sqlite/1700044187_curated_communities.up.sql diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index bd625be84..2514349bc 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -637,37 +637,45 @@ type CommunityShard struct { Shard *common.Shard `json:"shard"` } -type KnownCommunitiesResponse struct { - ContractCommunities []string `json:"contractCommunities"` // TODO: use CommunityShard - ContractFeaturedCommunities []string `json:"contractFeaturedCommunities"` // TODO: use CommunityShard - Descriptions map[string]*Community `json:"communities"` - UnknownCommunities []string `json:"unknownCommunities"` // TODO: use CommunityShard - +type CuratedCommunities struct { + ContractCommunities []string + ContractFeaturedCommunities []string } -func (m *Manager) GetStoredDescriptionForCommunities(communityIDs []types.HexBytes) (response *KnownCommunitiesResponse, err error) { - response = &KnownCommunitiesResponse{ +type KnownCommunitiesResponse struct { + ContractCommunities []string `json:"contractCommunities"` + ContractFeaturedCommunities []string `json:"contractFeaturedCommunities"` + Descriptions map[string]*Community `json:"communities"` + UnknownCommunities []string `json:"unknownCommunities"` +} + +func (m *Manager) GetStoredDescriptionForCommunities(communityIDs []string) (*KnownCommunitiesResponse, error) { + response := &KnownCommunitiesResponse{ Descriptions: make(map[string]*Community), } for i := range communityIDs { - communityID := communityIDs[i].String() - var community *Community - community, err = m.GetByID(communityIDs[i]) + communityID := communityIDs[i] + communityIDBytes, err := types.DecodeHex(communityID) if err != nil { - return + return nil, err } - response.ContractCommunities = append(response.ContractCommunities, communityID) + community, err := m.GetByID(types.HexBytes(communityIDBytes)) + if err != nil { + return nil, err + } if community != nil { response.Descriptions[community.IDString()] = community } else { response.UnknownCommunities = append(response.UnknownCommunities, communityID) } + + response.ContractCommunities = append(response.ContractCommunities, communityID) } - return + return response, nil } func (m *Manager) Joined() ([]*Community, error) { @@ -5107,3 +5115,11 @@ func (m *Manager) SafeGetSignerPubKey(chainID uint64, communityID string) (strin return m.ownerVerifier.SafeGetSignerPubKey(ctx, chainID, communityID) } + +func (m *Manager) GetCuratedCommunities() (*CuratedCommunities, error) { + return m.persistence.GetCuratedCommunities() +} + +func (m *Manager) SetCuratedCommunities(communities *CuratedCommunities) error { + return m.persistence.SetCuratedCommunities(communities) +} diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index 100a9b30c..0e2a6bb1e 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -1400,7 +1400,7 @@ func decodeEventsData(eventsBytes []byte, eventsDescriptionBytes []byte) (*Event func (p *Persistence) GetCommunityRequestsToJoinWithRevealedAddresses(communityID []byte) ([]*RequestToJoin, error) { requests := []*RequestToJoin{} rows, err := p.db.Query(` - SELECT r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.state, r.community_id, + SELECT r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.state, r.community_id, a.address, a.chain_ids, a.is_airdrop_address, a.signature FROM communities_requests_to_join r LEFT JOIN communities_requests_to_join_revealed_addresses a ON r.id = a.request_id @@ -1604,7 +1604,7 @@ func (p *Persistence) SaveRequestsToJoin(requests []*RequestToJoin) (err error) } }() - stmt, err := tx.Prepare(`INSERT OR REPLACE INTO communities_requests_to_join(id,public_key,clock,ens_name,chat_id,community_id,state) + stmt, err := tx.Prepare(`INSERT OR REPLACE INTO communities_requests_to_join(id,public_key,clock,ens_name,chat_id,community_id,state) VALUES (?, ?, ?, ?, ?, ?, ?)`) if err != nil { return err @@ -1632,3 +1632,70 @@ func (p *Persistence) SaveRequestsToJoin(requests []*RequestToJoin) (err error) err = tx.Commit() return err } + +func (p *Persistence) GetCuratedCommunities() (*CuratedCommunities, error) { + rows, err := p.db.Query("SELECT community_id, featured FROM curated_communities") + if err != nil { + return nil, err + } + defer rows.Close() + + result := &CuratedCommunities{ + ContractCommunities: []string{}, + ContractFeaturedCommunities: []string{}, + } + for rows.Next() { + var communityID string + var featured bool + if err := rows.Scan(&communityID, &featured); err != nil { + return nil, err + } + result.ContractCommunities = append(result.ContractCommunities, communityID) + if featured { + result.ContractFeaturedCommunities = append(result.ContractFeaturedCommunities, communityID) + } + } + + return result, nil +} + +func (p *Persistence) SetCuratedCommunities(communities *CuratedCommunities) error { + tx, err := p.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() + }() + + // Clear the existing communities + if _, err = tx.Exec("DELETE FROM curated_communities"); err != nil { + return err + } + + stmt, err := tx.Prepare("INSERT INTO curated_communities (community_id, featured) VALUES (?, ?)") + if err != nil { + return err + } + defer stmt.Close() + + featuredMap := make(map[string]bool) + for _, community := range communities.ContractFeaturedCommunities { + featuredMap[community] = true + } + + for _, community := range communities.ContractCommunities { + _, err := stmt.Exec(community, featuredMap[community]) + if err != nil { + return err + } + } + + return nil +} diff --git a/protocol/communities/persistence_test.go b/protocol/communities/persistence_test.go index 5515c5d23..401e0f3a8 100644 --- a/protocol/communities/persistence_test.go +++ b/protocol/communities/persistence_test.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "database/sql" "math/big" + "reflect" "testing" "time" @@ -634,3 +635,34 @@ func (s *PersistenceSuite) TestGetCommunityRequestsToJoinWithRevealedAddresses() s.Require().Len(rtjResult, 4) s.Require().Len(rtjResult[3].RevealedAccounts, 0) } + +func (s *PersistenceSuite) TestCuratedCommunities() { + communities, err := s.db.GetCuratedCommunities() + s.Require().NoError(err) + s.Require().Empty(communities.ContractCommunities) + s.Require().Empty(communities.ContractFeaturedCommunities) + + setCommunities := &CuratedCommunities{ + ContractCommunities: []string{"x", "d"}, + ContractFeaturedCommunities: []string{"x"}, + } + + err = s.db.SetCuratedCommunities(setCommunities) + s.Require().NoError(err) + + communities, err = s.db.GetCuratedCommunities() + s.Require().NoError(err) + s.Require().True(reflect.DeepEqual(communities, setCommunities)) + + setCommunities = &CuratedCommunities{ + ContractCommunities: []string{"p", "a", "t", "r", "y", "k"}, + ContractFeaturedCommunities: []string{"p", "k"}, + } + + err = s.db.SetCuratedCommunities(setCommunities) + s.Require().NoError(err) + + communities, err = s.db.GetCuratedCommunities() + s.Require().NoError(err) + s.Require().True(reflect.DeepEqual(communities, setCommunities)) +} diff --git a/protocol/messenger_curated_communities.go b/protocol/messenger_curated_communities.go index d2d42ba07..ea772382c 100644 --- a/protocol/messenger_curated_communities.go +++ b/protocol/messenger_curated_communities.go @@ -4,7 +4,6 @@ import ( "context" "errors" "reflect" - "sync" "time" "go.uber.org/zap" @@ -15,66 +14,56 @@ import ( ) const ( - fetchError int = 0 - fetchSuccess int = 1 - fetchHasUnknowns int = 2 + curatedCommunitiesUpdateInterval = 2 * time.Minute ) // Regularly gets list of curated communities and signals them to client func (m *Messenger) startCuratedCommunitiesUpdateLoop() { logger := m.logger.Named("startCuratedCommunitiesUpdateLoop") - type curatedCommunities struct { - ContractCommunities []string - ContractFeaturedCommunities []string - UnknownCommunities []string - } - go func() { + // Initialize interval to 0 for immediate execution + var interval time.Duration = 0 - var fetchResultsHistory = make([]int, 0) - var mu = sync.RWMutex{} - var c = curatedCommunities{} + cache, err := m.communitiesManager.GetCuratedCommunities() + if err != nil { + logger.Error("failed to start curated communities loop", zap.Error(err)) + return + } for { - response, err := m.CuratedCommunities() - - if err != nil { - fetchResultsHistory = append(fetchResultsHistory, fetchError) - } else { - mu.Lock() - // Check if it's the same values we had - if !reflect.DeepEqual(c.ContractCommunities, response.ContractCommunities) || - !reflect.DeepEqual(c.ContractFeaturedCommunities, response.ContractFeaturedCommunities) || - !reflect.DeepEqual(c.UnknownCommunities, response.UnknownCommunities) { - // One of the communities is different, send the updated response - m.config.messengerSignalsHandler.SendCuratedCommunitiesUpdate(response) - - // Update the values - c.ContractCommunities = response.ContractCommunities - c.ContractFeaturedCommunities = response.ContractFeaturedCommunities - c.UnknownCommunities = response.UnknownCommunities - } - mu.Unlock() - - if len(response.UnknownCommunities) == 0 { - fetchResultsHistory = append(fetchResultsHistory, fetchSuccess) - - } else { - fetchResultsHistory = append(fetchResultsHistory, fetchHasUnknowns) - } - } - - //keep only 2 last fetch results - if len(fetchResultsHistory) > 2 { - fetchResultsHistory = fetchResultsHistory[1:] - } - - timeTillNextUpdate := calcTimeTillNextUpdate(fetchResultsHistory) - logger.Debug("Next curated communities update will happen in", zap.Duration("timeTillNextUpdate", timeTillNextUpdate)) - select { - case <-time.After(timeTillNextUpdate): + case <-time.After(interval): + // Immediate execution on first run, then set to regular interval + interval = curatedCommunitiesUpdateInterval + + curatedCommunities, err := m.getCuratedCommunitiesFromContract() + if err != nil { + logger.Error("failed to get curated communities from contract", zap.Error(err)) + continue + } + + if reflect.DeepEqual(cache.ContractCommunities, curatedCommunities.ContractCommunities) && + reflect.DeepEqual(cache.ContractFeaturedCommunities, curatedCommunities.ContractFeaturedCommunities) { + // nothing changed + continue + } + + err = m.communitiesManager.SetCuratedCommunities(curatedCommunities) + if err == nil { + cache = curatedCommunities + } else { + logger.Error("failed to save curated communities", zap.Error(err)) + } + + response, err := m.fetchCuratedCommunities(curatedCommunities) + if err != nil { + logger.Error("failed to fetch curated communities", zap.Error(err)) + continue + } + + m.config.messengerSignalsHandler.SendCuratedCommunitiesUpdate(response) + case <-m.quit: return } @@ -82,39 +71,7 @@ func (m *Messenger) startCuratedCommunitiesUpdateLoop() { }() } -func calcTimeTillNextUpdate(fetchResultsHistory []int) time.Duration { - // TODO lower this back again once the real curated community contract is up - // The current contract contains communities that are no longer accessible on waku - const shortTimeout = 30 * time.Second - const averageTimeout = 60 * time.Second - const longTimeout = 300 * time.Second - - twoConsecutiveErrors := (len(fetchResultsHistory) == 2 && - fetchResultsHistory[0] == fetchError && - fetchResultsHistory[1] == fetchError) - - twoConsecutiveHasUnknowns := (len(fetchResultsHistory) == 2 && - fetchResultsHistory[0] == fetchHasUnknowns && - fetchResultsHistory[1] == fetchHasUnknowns) - - var timeTillNextUpdate time.Duration - - if twoConsecutiveErrors || twoConsecutiveHasUnknowns { - timeTillNextUpdate = longTimeout - } else { - switch fetchResultsHistory[len(fetchResultsHistory)-1] { - case fetchError: - timeTillNextUpdate = shortTimeout - case fetchSuccess: - timeTillNextUpdate = longTimeout - case fetchHasUnknowns: - timeTillNextUpdate = averageTimeout - } - } - return timeTillNextUpdate -} - -func (m *Messenger) CuratedCommunities() (*communities.KnownCommunitiesResponse, error) { +func (m *Messenger) getCuratedCommunitiesFromContract() (*communities.CuratedCommunities, error) { if m.contractMaker == nil { m.logger.Warn("contract maker not initialized") return nil, errors.New("contract maker not initialized") @@ -137,28 +94,36 @@ func (m *Messenger) CuratedCommunities() (*communities.KnownCommunitiesResponse, callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} - curatedCommunities, err := directory.GetCommunities(callOpts) + contractCommunities, err := directory.GetCommunities(callOpts) if err != nil { return nil, err } - var communityIDs []types.HexBytes - for _, c := range curatedCommunities { - communityIDs = append(communityIDs, c) + var contractCommunityIDs []string + for _, c := range contractCommunities { + contractCommunityIDs = append(contractCommunityIDs, types.HexBytes(c).String()) } - response, err := m.communitiesManager.GetStoredDescriptionForCommunities(communityIDs) + featuredContractCommunities, err := directory.GetFeaturedCommunities(callOpts) if err != nil { return nil, err } + var contractFeaturedCommunityIDs []string + for _, c := range featuredContractCommunities { + contractFeaturedCommunityIDs = append(contractFeaturedCommunityIDs, types.HexBytes(c).String()) + } - featuredCommunities, err := directory.GetFeaturedCommunities(callOpts) + return &communities.CuratedCommunities{ + ContractCommunities: contractCommunityIDs, + ContractFeaturedCommunities: contractFeaturedCommunityIDs, + }, nil +} + +func (m *Messenger) fetchCuratedCommunities(curatedCommunities *communities.CuratedCommunities) (*communities.KnownCommunitiesResponse, error) { + response, err := m.communitiesManager.GetStoredDescriptionForCommunities(curatedCommunities.ContractCommunities) if err != nil { return nil, err } - - for _, c := range featuredCommunities { - response.ContractFeaturedCommunities = append(response.ContractFeaturedCommunities, types.HexBytes(c).String()) - } + response.ContractFeaturedCommunities = curatedCommunities.ContractFeaturedCommunities // TODO: use mechanism to obtain shard from community ID (https://github.com/status-im/status-desktop/issues/12585) var unknownCommunities []communities.CommunityShard @@ -172,3 +137,11 @@ func (m *Messenger) CuratedCommunities() (*communities.KnownCommunitiesResponse, return response, nil } + +func (m *Messenger) CuratedCommunities() (*communities.KnownCommunitiesResponse, error) { + curatedCommunities, err := m.communitiesManager.GetCuratedCommunities() + if err != nil { + return nil, err + } + return m.fetchCuratedCommunities(curatedCommunities) +} diff --git a/protocol/migrations/migrations.go b/protocol/migrations/migrations.go index 1ad6f74c2..f2ae399a7 100644 --- a/protocol/migrations/migrations.go +++ b/protocol/migrations/migrations.go @@ -112,6 +112,7 @@ // 1699041816_profile_showcase_contacts.up.sql (2.206kB) // 1699554099_message_segments.up.sql (426B) // 1700044186_message_segments_timestamp.up.sql (322B) +// 1700044187_curated_communities.up.sql (131B) // README.md (554B) // doc.go (850B) @@ -2276,7 +2277,7 @@ func _1697699419_community_control_node_syncUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1697699419_community_control_node_sync.up.sql", size: 435, mode: os.FileMode(0644), modTime: time.Unix(1699030398, 0)} + info := bindataFileInfo{name: "1697699419_community_control_node_sync.up.sql", size: 435, mode: os.FileMode(0644), modTime: time.Unix(1700066424, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x11, 0xd6, 0x63, 0x10, 0x1b, 0x16, 0x35, 0x57, 0xf1, 0x4a, 0x4, 0x51, 0xe0, 0x1, 0xe1, 0xfc, 0x12, 0x3a, 0x10, 0x4f, 0xb1, 0x96, 0x53, 0x2, 0xf5, 0x66, 0x7b, 0xe0, 0x8a, 0xdf, 0x78, 0x53}} return a, nil } @@ -2296,7 +2297,7 @@ func _1698137561_add_profile_showcase_tablesUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1698137561_add_profile_showcase_tables.up.sql", size: 440, mode: os.FileMode(0644), modTime: time.Unix(1699030398, 0)} + info := bindataFileInfo{name: "1698137561_add_profile_showcase_tables.up.sql", size: 440, mode: os.FileMode(0644), modTime: time.Unix(1700066424, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7c, 0xef, 0x89, 0x68, 0x42, 0xbf, 0xff, 0xb9, 0x8f, 0x8f, 0x19, 0x91, 0xd2, 0x6a, 0x85, 0xda, 0x2c, 0x63, 0x5f, 0x3c, 0x84, 0x4, 0x93, 0x16, 0x10, 0xf0, 0xe0, 0xd9, 0x9b, 0xbe, 0x8d, 0x62}} return a, nil } @@ -2316,7 +2317,7 @@ func _1698137562_fix_encryption_key_idUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1698137562_fix_encryption_key_id.up.sql", size: 758, mode: os.FileMode(0644), modTime: time.Unix(1699030398, 0)} + info := bindataFileInfo{name: "1698137562_fix_encryption_key_id.up.sql", size: 758, mode: os.FileMode(0644), modTime: time.Unix(1700066424, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe5, 0x61, 0x1b, 0x6a, 0xb1, 0x44, 0x8d, 0x47, 0xde, 0x55, 0x45, 0x77, 0x8e, 0x4f, 0xb, 0x6a, 0x7f, 0x83, 0x56, 0x9c, 0x80, 0xc0, 0xae, 0xda, 0xd8, 0xaf, 0x7e, 0x2b, 0xb4, 0x5e, 0xc3, 0x63}} return a, nil } @@ -2336,7 +2337,7 @@ func _1698414646_add_paddingUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1698414646_add_padding.up.sql", size: 69, mode: os.FileMode(0644), modTime: time.Unix(1699030398, 0)} + info := bindataFileInfo{name: "1698414646_add_padding.up.sql", size: 69, mode: os.FileMode(0644), modTime: time.Unix(1700066424, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xbf, 0x48, 0x8e, 0x18, 0x1b, 0x81, 0x78, 0xab, 0x42, 0xcb, 0x11, 0xf5, 0xe, 0x44, 0xd4, 0x35, 0x33, 0x4e, 0x8, 0x6f, 0x14, 0x90, 0xe6, 0x2b, 0x59, 0xee, 0x87, 0xb, 0x96, 0x62, 0x3, 0x45}} return a, nil } @@ -2356,7 +2357,7 @@ func _1698746210_add_signature_to_revealed_addressesUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1698746210_add_signature_to_revealed_addresses.up.sql", size: 87, mode: os.FileMode(0644), modTime: time.Unix(1699030398, 0)} + info := bindataFileInfo{name: "1698746210_add_signature_to_revealed_addresses.up.sql", size: 87, mode: os.FileMode(0644), modTime: time.Unix(1700066424, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8f, 0x64, 0xef, 0xe7, 0x5d, 0x82, 0x3e, 0x7d, 0x5a, 0x34, 0xd2, 0xa, 0x5c, 0x48, 0xef, 0x40, 0xb4, 0x7d, 0x78, 0xc8, 0x11, 0xbc, 0xf3, 0xc5, 0x1d, 0xd5, 0xe9, 0x39, 0xd9, 0xfa, 0xc8, 0x27}} return a, nil } @@ -2376,7 +2377,7 @@ func _1699041816_profile_showcase_contactsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1699041816_profile_showcase_contacts.up.sql", size: 2206, mode: os.FileMode(0644), modTime: time.Unix(1699887700, 0)} + info := bindataFileInfo{name: "1699041816_profile_showcase_contacts.up.sql", size: 2206, mode: os.FileMode(0644), modTime: time.Unix(1700066424, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd5, 0x7b, 0x55, 0xda, 0x93, 0x4a, 0x92, 0xf8, 0x45, 0xb2, 0x9f, 0x32, 0xf4, 0x37, 0xc, 0x5f, 0x62, 0xba, 0x33, 0xe2, 0x5c, 0x91, 0x1c, 0xc, 0x7, 0x9, 0xc2, 0x27, 0x5, 0x90, 0x94, 0xf3}} return a, nil } @@ -2396,7 +2397,7 @@ func _1699554099_message_segmentsUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1699554099_message_segments.up.sql", size: 426, mode: os.FileMode(0644), modTime: time.Unix(1699976109, 0)} + info := bindataFileInfo{name: "1699554099_message_segments.up.sql", size: 426, mode: os.FileMode(0644), modTime: time.Unix(1700066424, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x73, 0xca, 0xd, 0xfa, 0xfa, 0x17, 0xef, 0x7e, 0x24, 0xf9, 0x28, 0xbd, 0x39, 0x75, 0xff, 0x34, 0x31, 0x27, 0x58, 0x3c, 0x17, 0x77, 0xfd, 0xc2, 0x66, 0x47, 0x63, 0x58, 0x3e, 0xb3, 0x88, 0x1a}} return a, nil } @@ -2416,11 +2417,31 @@ func _1700044186_message_segments_timestampUpSql() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "1700044186_message_segments_timestamp.up.sql", size: 322, mode: os.FileMode(0644), modTime: time.Unix(1700049804, 0)} + info := bindataFileInfo{name: "1700044186_message_segments_timestamp.up.sql", size: 322, mode: os.FileMode(0644), modTime: time.Unix(1700066437, 0)} a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x3e, 0x4e, 0x7, 0x86, 0x71, 0xc8, 0x1f, 0x2f, 0xf4, 0xbc, 0xc5, 0xc4, 0x37, 0x56, 0xa1, 0x47, 0xd9, 0xc9, 0xfd, 0xdf, 0x9a, 0x48, 0x1d, 0xfd, 0xb4, 0xeb, 0xb6, 0xb1, 0xc2, 0x73, 0x11, 0x19}} return a, nil } +var __1700044187_curated_communitiesUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\xcc\xb1\x0a\xc2\x30\x10\x87\xf1\xbd\x4f\xf1\x1f\x15\x7c\x03\xa7\xab\x5e\x21\x78\x26\x92\x5c\xa1\x9d\x4a\x69\x22\x64\xa8\x42\x4d\x06\xdf\x5e\x28\xb8\x7e\x7c\xfc\x2e\x9e\x49\x19\x4a\xad\x30\x4c\x07\xeb\x14\x3c\x98\xa0\x01\x4b\xdd\xe6\x92\xe2\xb4\xbc\xd7\xb5\xbe\x72\xc9\xe9\x83\x43\x03\x00\xff\xf2\x9d\x72\x84\xf2\xa0\x78\x78\x73\x27\x3f\xe2\xc6\xe3\x69\x5f\x9e\x69\x2e\x75\x4b\x11\xad\x73\xc2\x64\x77\xd8\xf6\x22\xb8\x72\x47\xbd\x28\x3a\x92\xc0\xcd\xf1\xdc\xfc\x02\x00\x00\xff\xff\xb5\x80\x91\xfe\x83\x00\x00\x00") + +func _1700044187_curated_communitiesUpSqlBytes() ([]byte, error) { + return bindataRead( + __1700044187_curated_communitiesUpSql, + "1700044187_curated_communities.up.sql", + ) +} + +func _1700044187_curated_communitiesUpSql() (*asset, error) { + bytes, err := _1700044187_curated_communitiesUpSqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "1700044187_curated_communities.up.sql", size: 131, mode: os.FileMode(0644), modTime: time.Unix(1700066437, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xed, 0xf1, 0xf1, 0x57, 0xb5, 0x83, 0xad, 0x9d, 0x9b, 0xf, 0x49, 0xe, 0x3d, 0xa5, 0xf6, 0xf5, 0x9c, 0x7f, 0xb3, 0xf7, 0x22, 0x43, 0x8a, 0xa0, 0x49, 0xfa, 0xcc, 0x9b, 0xea, 0xac, 0xc0, 0xb9}} + return a, nil +} + var _readmeMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x91\xc1\xce\xd3\x30\x10\x84\xef\x7e\x8a\x91\x7a\x01\xa9\x2a\x8f\xc0\x0d\x71\x82\x03\x48\x1c\xc9\x36\x9e\x36\x96\x1c\x6f\xf0\xae\x93\xe6\xed\x91\xa3\xc2\xdf\xff\x66\xed\xd8\x33\xdf\x78\x4f\xa7\x13\xbe\xea\x06\x57\x6c\x35\x39\x31\xa7\x7b\x15\x4f\x5a\xec\x73\x08\xbf\x08\x2d\x79\x7f\x4a\x43\x5b\x86\x17\xfd\x8c\x21\xea\x56\x5e\x47\x90\x4a\x14\x75\x48\xde\x64\x37\x2c\x6a\x96\xae\x99\x48\x05\xf6\x27\x77\x13\xad\x08\xae\x8a\x51\xe7\x25\xf3\xf1\xa9\x9f\xf9\x58\x58\x2c\xad\xbc\xe0\x8b\x56\xf0\x21\x5d\xeb\x4c\x95\xb3\xae\x84\x60\xd4\xdc\xe6\x82\x5d\x1b\x36\x6d\x39\x62\x92\xf5\xb8\x11\xdb\x92\xd3\x28\xce\xe0\x13\xe1\x72\xcd\x3c\x63\xd4\x65\x87\xae\xac\xe8\xc3\x28\x2e\x67\x44\x66\x3a\x21\x25\xa2\x72\xac\x14\x67\xbc\x84\x9f\x53\x32\x8c\x52\x70\x25\x56\xd6\xfd\x8d\x05\x37\xad\x30\x9d\x9f\xa6\x86\x0f\xcd\x58\x7f\xcf\x34\x93\x3b\xed\x90\x9f\xa4\x1f\xcf\x30\x85\x4d\x07\x58\xaf\x7f\x25\xc4\x9d\xf3\x72\x64\x84\xd0\x7f\xf9\x9b\x3a\x2d\x84\xef\x85\x48\x66\x8d\xd8\x88\x9b\x8c\x8c\x98\x5b\xf6\x74\x14\x4e\x33\x0d\xc9\xe0\x93\x38\xda\x12\xc5\x69\xbd\xe4\xf0\x2e\x7a\x78\x07\x1c\xfe\x13\x9f\x91\x29\x31\x95\x7b\x7f\x62\x59\x37\xb4\xe5\x5e\x25\xfe\x33\xee\xd5\x53\x71\xd6\xda\x3a\xd8\xcb\xde\x2e\xf8\xa1\x90\x55\x53\x0c\xc7\xaa\x0d\xe9\x76\x14\x29\x1c\x7b\x68\xdd\x2f\xe1\x6f\x00\x00\x00\xff\xff\x3c\x0a\xc2\xfe\x2a\x02\x00\x00") func readmeMdBytes() ([]byte, error) { @@ -2664,8 +2685,9 @@ var _bindata = map[string]func() (*asset, error){ "1699041816_profile_showcase_contacts.up.sql": _1699041816_profile_showcase_contactsUpSql, "1699554099_message_segments.up.sql": _1699554099_message_segmentsUpSql, "1700044186_message_segments_timestamp.up.sql": _1700044186_message_segments_timestampUpSql, - "README.md": readmeMd, - "doc.go": docGo, + "1700044187_curated_communities.up.sql": _1700044187_curated_communitiesUpSql, + "README.md": readmeMd, + "doc.go": docGo, } // AssetDebug is true if the assets were built with the debug flag enabled. @@ -2826,8 +2848,9 @@ var _bintree = &bintree{nil, map[string]*bintree{ "1699041816_profile_showcase_contacts.up.sql": {_1699041816_profile_showcase_contactsUpSql, map[string]*bintree{}}, "1699554099_message_segments.up.sql": {_1699554099_message_segmentsUpSql, map[string]*bintree{}}, "1700044186_message_segments_timestamp.up.sql": {_1700044186_message_segments_timestampUpSql, map[string]*bintree{}}, - "README.md": {readmeMd, map[string]*bintree{}}, - "doc.go": {docGo, map[string]*bintree{}}, + "1700044187_curated_communities.up.sql": {_1700044187_curated_communitiesUpSql, map[string]*bintree{}}, + "README.md": {readmeMd, map[string]*bintree{}}, + "doc.go": {docGo, map[string]*bintree{}}, }} // RestoreAsset restores an asset under the given directory. diff --git a/protocol/migrations/sqlite/1700044187_curated_communities.up.sql b/protocol/migrations/sqlite/1700044187_curated_communities.up.sql new file mode 100644 index 000000000..682a69b33 --- /dev/null +++ b/protocol/migrations/sqlite/1700044187_curated_communities.up.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS curated_communities ( + community_id TEXT PRIMARY KEY, + featured BOOLEAN NOT NULL DEFAULT FALSE +);