Introduce `CommunitySettings` to store community related settings

These are used to store settings for individual communities a
user is part of, either as member or as owner.
This included whether or not the community history archive protocol
is enabled.

This adds a new `CommunitySettings` type and adds
a migration script that introduces a new `communities_settings`
table.

It also extends the `MessengerResponse` type to include
`CommunitySettings` which are honored when communities are being
added, edited, joined or left.

Lastly, this adds a new RPC API to retreive the settings.

Closes #2564
This commit is contained in:
Pascal Precht 2022-03-08 16:25:00 +01:00 committed by r4bbit.eth
parent 12ccff417d
commit bf8e71cfa9
12 changed files with 322 additions and 15 deletions

View File

@ -8,6 +8,7 @@
// 1646841105_add_emoji_account.up.sql (96B)
// 1647278782_display_name.up.sql (110B)
// 1647860168_add_torrent_config.up.sql (211B)
// 1647862837_add_communities_settings_table.up.sql (206B)
// doc.go (74B)
package migrations
@ -232,11 +233,31 @@ func _1647860168_add_torrent_configUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1647860168_add_torrent_config.up.sql", size: 211, mode: os.FileMode(0664), modTime: time.Unix(1647860188, 0)}
info := bindataFileInfo{name: "1647860168_add_torrent_config.up.sql", size: 211, mode: os.FileMode(0664), modTime: time.Unix(1647862710, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1, 0x92, 0x22, 0x37, 0x96, 0xf3, 0xb5, 0x5b, 0x27, 0xd0, 0x7d, 0x43, 0x5, 0x4e, 0x9d, 0xe2, 0x49, 0xbe, 0x86, 0x31, 0xa1, 0x89, 0xff, 0xd6, 0x51, 0xe0, 0x9c, 0xb, 0xda, 0xfc, 0xf2, 0x93}}
return a, nil
}
var __1647862837_add_communities_settings_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\xcb\xb1\xaa\xc2\x30\x14\x06\xe0\x3d\x4f\xf1\x8f\xf7\x82\x2f\x71\x1a\x4f\xa1\x18\x9b\x12\x23\xd8\x29\xd4\xf6\xd8\x06\x6c\x04\x13\x05\xdf\xde\xcd\x49\x70\xfe\xf8\xb4\x63\xf2\x0c\x4f\x95\x61\x8c\xb7\x75\x7d\xa4\x58\xa2\xe4\x90\xa5\x94\x98\xe6\x8c\x3f\x85\x0f\xbc\x42\x9c\xe0\xf9\xe4\xd1\xb9\x66\x4f\xae\xc7\x8e\x7b\xd8\x16\xda\xb6\xb5\x69\xb4\x87\xe3\xce\x90\xe6\x8d\x02\x56\xc9\x79\x98\x25\x0c\xf7\x71\x89\x4f\x09\x59\x64\x8a\x69\x0e\x92\x86\xf3\x55\x26\x54\xd6\x1a\xa6\x16\x5b\xae\xe9\x68\x3c\x6a\x32\x87\xaf\xf1\x22\x65\x5c\x7e\x4e\xf5\xaf\xd4\x3b\x00\x00\xff\xff\x01\x6b\xfa\x19\xce\x00\x00\x00")
func _1647862837_add_communities_settings_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
__1647862837_add_communities_settings_tableUpSql,
"1647862837_add_communities_settings_table.up.sql",
)
}
func _1647862837_add_communities_settings_tableUpSql() (*asset, error) {
bytes, err := _1647862837_add_communities_settings_tableUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "1647862837_add_communities_settings_table.up.sql", size: 206, mode: os.FileMode(0664), modTime: time.Unix(1647862858, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xbd, 0x87, 0x78, 0x99, 0xd9, 0x5d, 0xbd, 0xf7, 0x57, 0x9c, 0xca, 0x97, 0xbd, 0xb3, 0xe9, 0xb5, 0x89, 0x31, 0x3f, 0xf6, 0x5c, 0x13, 0xb, 0xc3, 0x54, 0x93, 0x18, 0x40, 0x7, 0x82, 0xfe, 0x7e}}
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) {
@ -364,6 +385,8 @@ var _bindata = map[string]func() (*asset, error){
"1647860168_add_torrent_config.up.sql": _1647860168_add_torrent_configUpSql,
"1647862837_add_communities_settings_table.up.sql": _1647862837_add_communities_settings_tableUpSql,
"doc.go": docGo,
}
@ -416,7 +439,8 @@ var _bintree = &bintree{nil, map[string]*bintree{
"1646841105_add_emoji_account.up.sql": &bintree{_1646841105_add_emoji_accountUpSql, map[string]*bintree{}},
"1647278782_display_name.up.sql": &bintree{_1647278782_display_nameUpSql, map[string]*bintree{}},
"1647860168_add_torrent_config.up.sql": &bintree{_1647860168_add_torrent_configUpSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}},
"1647862837_add_communities_settings_table.up.sql": &bintree{_1647862837_add_communities_settings_tableUpSql, map[string]*bintree{}},
"doc.go": &bintree{docGo, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory.

View File

@ -0,0 +1,6 @@
CREATE TABLE communities_settings (
community_id TEXT PRIMARY KEY ON CONFLICT REPLACE,
message_archive_seeding_enabled BOOLEAN DEFAULT FALSE,
message_archive_fetching_enabled BOOLEAN DEFAULT FALSE
)

View File

@ -269,6 +269,11 @@ func (o *Community) initialize() {
}
}
type CommunitySettings struct {
CommunityID string `json:"communityId"`
HistoryArchiveSupportEnabled bool `json:"historyArchiveSupportEnabled"`
}
type CommunityChatChanges struct {
ChatModified *protobuf.CommunityChat
MembersAdded map[string]*protobuf.CommunityMember

View File

@ -916,3 +916,27 @@ func (m *Manager) SetPrivateKey(id []byte, privKey *ecdsa.PrivateKey) error {
func (m *Manager) GetSyncedRawCommunity(id []byte) (*rawCommunityRow, error) {
return m.persistence.getSyncedRawCommunity(id)
}
func (m *Manager) GetCommunitySettingsByID(id types.HexBytes) (*CommunitySettings, error) {
return m.persistence.GetCommunitySettingsByID(id)
}
func (m *Manager) GetCommunitiesSettings() ([]CommunitySettings, error) {
return m.persistence.GetCommunitiesSettings()
}
func (m *Manager) SaveCommunitySettings(settings CommunitySettings) error {
return m.persistence.SaveCommunitySettings(settings)
}
func (m *Manager) CommunitySettingsExist(id types.HexBytes) (bool, error) {
return m.persistence.CommunitySettingsExist(id)
}
func (m *Manager) DeleteCommunitySettings(id types.HexBytes) error {
return m.persistence.DeleteCommunitySettings(id)
}
func (m *Manager) UpdateCommunitySettings(settings CommunitySettings) error {
return m.persistence.UpdateCommunitySettings(settings)
}

View File

@ -10,6 +10,7 @@ import (
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
"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"
)
@ -345,3 +346,72 @@ func (p *Persistence) SetPrivateKey(id []byte, privKey *ecdsa.PrivateKey) error
_, err := p.db.Exec(`UPDATE communities_communities SET private_key = ? WHERE id = ?`, crypto.FromECDSA(privKey), id)
return err
}
func (p *Persistence) GetCommunitiesSettings() ([]CommunitySettings, error) {
rows, err := p.db.Query("SELECT community_id, message_archive_seeding_enabled, message_archive_fetching_enabled FROM communities_settings")
if err != nil {
return nil, err
}
defer rows.Close()
communitiesSettings := []CommunitySettings{}
for rows.Next() {
settings := CommunitySettings{}
err := rows.Scan(&settings.CommunityID, &settings.HistoryArchiveSupportEnabled, &settings.HistoryArchiveSupportEnabled)
if err != nil {
return nil, err
}
communitiesSettings = append(communitiesSettings, settings)
}
return communitiesSettings, err
}
func (p *Persistence) CommunitySettingsExist(communityID types.HexBytes) (bool, error) {
var count int
err := p.db.QueryRow(`SELECT count(1) FROM communities_settings WHERE community_id = ?`, communityID.String()).Scan(&count)
if err != nil {
return false, err
}
return count > 0, nil
}
func (p *Persistence) GetCommunitySettingsByID(communityID types.HexBytes) (*CommunitySettings, error) {
settings := CommunitySettings{}
err := p.db.QueryRow(`SELECT community_id, message_archive_seeding_enabled, message_archive_fetching_enabled FROM communities_settings WHERE community_id = ?`, communityID.String()).Scan(&settings.CommunityID, &settings.HistoryArchiveSupportEnabled, &settings.HistoryArchiveSupportEnabled)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return &settings, nil
}
func (p *Persistence) DeleteCommunitySettings(communityID types.HexBytes) error {
_, err := p.db.Exec("DELETE FROM communities_settings WHERE community_id = ?", communityID.String())
return err
}
func (p *Persistence) SaveCommunitySettings(communitySettings CommunitySettings) error {
_, err := p.db.Exec(`INSERT INTO communities_settings (
community_id,
message_archive_seeding_enabled,
message_archive_fetching_enabled
) VALUES (?, ?, ?)`,
communitySettings.CommunityID,
communitySettings.HistoryArchiveSupportEnabled,
communitySettings.HistoryArchiveSupportEnabled,
)
return err
}
func (p *Persistence) UpdateCommunitySettings(communitySettings CommunitySettings) error {
_, err := p.db.Exec(`UPDATE communities_settings SET
message_archive_seeding_enabled = ?,
message_archive_fetching_enabled = ?
WHERE community_id = ?`,
communitySettings.HistoryArchiveSupportEnabled,
communitySettings.HistoryArchiveSupportEnabled,
communitySettings.CommunityID,
)
return err
}

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/common"
@ -32,9 +33,12 @@ func (s *PersistenceSuite) SetupTest() {
dbPath, err := ioutil.TempFile("", "")
s.NoError(err, "creating temp file for db")
db, err := sqlite.Open(dbPath.Name(), "")
db, err := appdatabase.InitializeDB(dbPath.Name(), "")
s.NoError(err, "creating sqlite db instance")
err = sqlite.Migrate(db)
s.NoError(err, "protocol migrate")
s.db = &Persistence{db: db}
}
@ -255,3 +259,65 @@ func (s *PersistenceSuite) TestGetSyncedRawCommunity() {
s.NotNil(src)
s.Equal(clock, src.SyncedAt)
}
func (s *PersistenceSuite) TestGetCommunitiesSettings() {
settings := []CommunitySettings{
{CommunityID: "0x01", HistoryArchiveSupportEnabled: false},
{CommunityID: "0x02", HistoryArchiveSupportEnabled: true},
{CommunityID: "0x03", HistoryArchiveSupportEnabled: false},
}
for i := range settings {
stg := settings[i]
err := s.db.SaveCommunitySettings(stg)
s.NoError(err)
}
rst, err := s.db.GetCommunitiesSettings()
s.NoError(err)
s.Equal(settings, rst)
}
func (s *PersistenceSuite) TestSaveCommunitySettings() {
settings := CommunitySettings{CommunityID: "0x01", HistoryArchiveSupportEnabled: false}
err := s.db.SaveCommunitySettings(settings)
s.NoError(err)
rst, err := s.db.GetCommunitiesSettings()
s.NoError(err)
s.Equal(1, len(rst))
}
func (s *PersistenceSuite) TestDeleteCommunitySettings() {
settings := CommunitySettings{CommunityID: "0x01", HistoryArchiveSupportEnabled: false}
err := s.db.SaveCommunitySettings(settings)
s.NoError(err)
rst, err := s.db.GetCommunitiesSettings()
s.NoError(err)
s.Equal(1, len(rst))
s.NoError(s.db.DeleteCommunitySettings(types.HexBytes{0x01}))
rst2, err := s.db.GetCommunitiesSettings()
s.NoError(err)
s.Equal(0, len(rst2))
}
func (s *PersistenceSuite) TestUpdateCommunitySettings() {
settings := []CommunitySettings{
{CommunityID: "0x01", HistoryArchiveSupportEnabled: true},
{CommunityID: "0x02", HistoryArchiveSupportEnabled: false},
}
s.NoError(s.db.SaveCommunitySettings(settings[0]))
s.NoError(s.db.SaveCommunitySettings(settings[1]))
settings[0].HistoryArchiveSupportEnabled = true
settings[1].HistoryArchiveSupportEnabled = false
s.NoError(s.db.UpdateCommunitySettings(settings[0]))
s.NoError(s.db.UpdateCommunitySettings(settings[1]))
rst, err := s.db.GetCommunitiesSettings()
s.NoError(err)
s.Equal(settings, rst)
}

View File

@ -146,7 +146,13 @@ func (s *MessengerCommunitiesSuite) TestRetrieveCommunity() {
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.CommunitiesSettings(), 1)
community := response.Communities()[0]
communitySettings := response.CommunitiesSettings()[0]
s.Require().Equal(communitySettings.CommunityID, community.IDString())
s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
// Send an community message
chat := CreateOneToOneChat(common.PubkeyToHex(&alice.identity.PublicKey), &alice.identity.PublicKey, s.alice.transport)
@ -197,9 +203,14 @@ func (s *MessengerCommunitiesSuite) TestJoinCommunity() {
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.CommunitiesSettings(), 1)
communitySettings := response.CommunitiesSettings()[0]
community := response.Communities()[0]
s.Require().Equal(communitySettings.CommunityID, community.IDString())
s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
orgChat := &protobuf.CommunityChat{
Permissions: &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
@ -541,8 +552,13 @@ func (s *MessengerCommunitiesSuite) TestImportCommunity() {
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.CommunitiesSettings(), 1)
community := response.Communities()[0]
communitySettings := response.CommunitiesSettings()[0]
s.Require().Equal(communitySettings.CommunityID, community.IDString())
s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
category := &requests.CreateCommunityCategory{
CommunityID: community.ID(),

View File

@ -1244,6 +1244,28 @@ func (m *Messenger) Init() error {
for _, org := range joinedCommunities {
// the org advertise on the public topic derived by the pk
publicChatIDs = append(publicChatIDs, org.IDString(), org.StatusUpdatesChannelID(), org.MagnetlinkMessageChannelID())
// This is for status-go versions that didn't have `CommunitySettings`
// We need to ensure communities that existed before community settings
// were introduced will have community settings as well
exists, err := m.communitiesManager.CommunitySettingsExist(org.ID())
if err != nil {
logger.Warn("failed to check if community settings exist", zap.Error(err))
continue
}
if exists {
continue
}
communitySettings := communities.CommunitySettings{
CommunityID: org.IDString(),
HistoryArchiveSupportEnabled: false,
}
err = m.communitiesManager.SaveCommunitySettings(communitySettings)
if err != nil {
logger.Warn("failed to save community settings", zap.Error(err))
continue
}
}
// Init filters for the communities we are an admin of

View File

@ -137,6 +137,18 @@ func (m *Messenger) JoinCommunity(ctx context.Context, communityID types.HexByte
return nil, err
}
communitySettings := communities.CommunitySettings{
CommunityID: communityID.String(),
HistoryArchiveSupportEnabled: false,
}
err = m.communitiesManager.SaveCommunitySettings(communitySettings)
if err != nil {
return nil, err
}
mr.AddCommunitySettings(&communitySettings)
if com, ok := mr.communities[communityID.String()]; ok {
err = m.syncCommunity(context.Background(), com)
if err != nil {
@ -400,6 +412,11 @@ func (m *Messenger) LeaveCommunity(communityID types.HexBytes) (*MessengerRespon
return nil, err
}
err = m.communitiesManager.DeleteCommunitySettings(communityID)
if err != nil {
return nil, err
}
if com, ok := mr.communities[communityID.String()]; ok {
err = m.syncCommunity(context.Background(), com)
if err != nil {
@ -546,6 +563,15 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
return nil, err
}
communitySettings := communities.CommunitySettings{
CommunityID: community.IDString(),
HistoryArchiveSupportEnabled: request.HistoryArchiveSupportEnabled,
}
err = m.communitiesManager.SaveCommunitySettings(communitySettings)
if err != nil {
return nil, err
}
// Init the community filter so we can receive messages on the community
_, err = m.transport.InitCommunityFilters([]*ecdsa.PrivateKey{community.PrivateKey()})
if err != nil {
@ -560,6 +586,7 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
response := &MessengerResponse{}
response.AddCommunity(community)
response.AddCommunitySettings(&communitySettings)
err = m.syncCommunity(context.Background(), community)
if err != nil {
return nil, err
@ -578,8 +605,18 @@ func (m *Messenger) EditCommunity(request *requests.EditCommunity) (*MessengerRe
return nil, err
}
communitySettings := communities.CommunitySettings{
CommunityID: community.IDString(),
HistoryArchiveSupportEnabled: request.HistoryArchiveSupportEnabled,
}
err = m.communitiesManager.UpdateCommunitySettings(communitySettings)
if err != nil {
return nil, err
}
response := &MessengerResponse{}
response.AddCommunity(community)
response.AddCommunitySettings(&communitySettings)
return response, nil
}
@ -610,7 +647,12 @@ func (m *Messenger) ImportCommunity(ctx context.Context, key *ecdsa.PrivateKey)
return nil, err
}
return m.JoinCommunity(ctx, community.ID())
response, err := m.JoinCommunity(ctx, community.ID())
if err != nil {
return nil, err
}
return response, nil
}
func (m *Messenger) InviteUsersToCommunity(request *requests.InviteUsersToCommunity) (*MessengerResponse, error) {
@ -1055,3 +1097,11 @@ func (m *Messenger) handleSyncCommunity(messageState *ReceivedMessageState, sync
return nil
}
func (m *Messenger) GetCommunitiesSettings() ([]communities.CommunitySettings, error) {
settings, err := m.communitiesManager.GetCommunitiesSettings()
if err != nil {
return nil, err
}
return settings, nil
}

View File

@ -41,6 +41,7 @@ type MessengerResponse struct {
removedChats map[string]bool
removedMessages map[string]*RemovedMessage
communities map[string]*communities.Community
communitiesSettings map[string]*communities.CommunitySettings
activityCenterNotifications map[string]*ActivityCenterNotification
messages map[string]*common.Message
pinMessages map[string]*common.PinMessage
@ -69,6 +70,7 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
// that are useful to notify the user about
Notifications []*localnotifications.Notification `json:"notifications"`
Communities []*communities.Community `json:"communities,omitempty"`
CommunitiesSettings []*communities.CommunitySettings `json:"communitiesSettings,omitempty"`
ActivityCenterNotifications []*ActivityCenterNotification `json:"activityCenterNotifications,omitempty"`
CurrentStatus *UserStatus `json:"currentStatus,omitempty"`
StatusUpdates []UserStatus `json:"statusUpdates,omitempty"`
@ -88,6 +90,7 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
responseItem.Notifications = r.Notifications()
responseItem.Chats = r.Chats()
responseItem.Communities = r.Communities()
responseItem.CommunitiesSettings = r.CommunitiesSettings()
responseItem.RemovedChats = r.RemovedChats()
responseItem.RemovedMessages = r.RemovedMessages()
responseItem.ClearedHistories = r.ClearedHistories()
@ -138,6 +141,14 @@ func (r *MessengerResponse) Communities() []*communities.Community {
return communities
}
func (r *MessengerResponse) CommunitiesSettings() []*communities.CommunitySettings {
var settings []*communities.CommunitySettings
for _, s := range r.communitiesSettings {
settings = append(settings, s)
}
return settings
}
func (r *MessengerResponse) Notifications() []*localnotifications.Notification {
var notifications []*localnotifications.Notification
for _, n := range r.notifications {
@ -227,6 +238,14 @@ func (r *MessengerResponse) AddCommunity(c *communities.Community) {
r.communities[c.IDString()] = c
}
func (r *MessengerResponse) AddCommunitySettings(c *communities.CommunitySettings) {
if r.communitiesSettings == nil {
r.communitiesSettings = make(map[string]*communities.CommunitySettings)
}
r.communitiesSettings[c.CommunityID] = c
}
func (r *MessengerResponse) AddBookmark(bookmark *browsers.Bookmark) {
r.Bookmarks = append(r.Bookmarks, bookmark)
}

View File

@ -17,17 +17,18 @@ var (
)
type CreateCommunity struct {
Name string `json:"name"`
Description string `json:"description"`
Color string `json:"color"`
Emoji string `json:"emoji"`
Membership protobuf.CommunityPermissions_Access `json:"membership"`
EnsOnly bool `json:"ensOnly"`
Image string `json:"image"`
ImageAx int `json:"imageAx"`
ImageAy int `json:"imageAy"`
ImageBx int `json:"imageBx"`
ImageBy int `json:"imageBy"`
Name string `json:"name"`
Description string `json:"description"`
Color string `json:"color"`
Emoji string `json:"emoji"`
Membership protobuf.CommunityPermissions_Access `json:"membership"`
EnsOnly bool `json:"ensOnly"`
Image string `json:"image"`
ImageAx int `json:"imageAx"`
ImageAy int `json:"imageAy"`
ImageBx int `json:"imageBx"`
ImageBy int `json:"imageBy"`
HistoryArchiveSupportEnabled bool `json:"historyArchiveSupportEnabled,omitempty"`
}
func adaptIdentityImageToProtobuf(img *userimages.IdentityImage) *protobuf.IdentityImage {

View File

@ -924,6 +924,10 @@ func (api *PublicAPI) StopDiscV5() error {
return api.service.messenger.StopDiscV5()
}
func (api *PublicAPI) GetCommunitiesSettings() ([]communities.CommunitySettings, error) {
return api.service.messenger.GetCommunitiesSettings()
}
func (api *PublicAPI) AddStorePeer(address string) (string, error) {
return api.service.messenger.AddStorePeer(address)
}