Skip to content

Commit

Permalink
feat: add SpectateCommunity api
Browse files Browse the repository at this point in the history
- added `SpectateCommunity` endpoint, it is supposed to be used in
  scenarios where we want to "Go to public Community" and see its
  content without joining
- added `spectated` field to `Community`, it means we are observing the
  community and its chats but we are not members

Use case:
status-im/status-desktop#7072 (comment)
  • Loading branch information
osmaczko committed Sep 23, 2022
1 parent 5668bba commit 480aee7
Show file tree
Hide file tree
Showing 13 changed files with 433 additions and 243 deletions.
16 changes: 14 additions & 2 deletions protocol/communities/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Config struct {
Joined bool
Requested bool
Verified bool
Spectated bool
Muted bool
Logger *zap.Logger
RequestedToJoinAt uint64
Expand Down Expand Up @@ -186,6 +187,7 @@ func (o *Community) MarshalJSON() ([]byte, error) {
Admin bool `json:"admin"`
Verified bool `json:"verified"`
Joined bool `json:"joined"`
Spectated bool `json:"spectated"`
RequestedAccessAt int `json:"requestedAccessAt"`
Name string `json:"name"`
Description string `json:"description"`
Expand Down Expand Up @@ -214,6 +216,7 @@ func (o *Community) MarshalJSON() ([]byte, error) {
Chats: make(map[string]CommunityChat),
Categories: make(map[string]CommunityCategory),
Joined: o.config.Joined,
Spectated: o.config.Spectated,
CanRequestAccess: o.CanRequestAccess(o.config.MemberIdentity),
CanJoin: o.canJoin(),
CanManageUsers: o.CanManageUsers(o.config.MemberIdentity),
Expand Down Expand Up @@ -816,6 +819,11 @@ func (o *Community) Join() {

func (o *Community) Leave() {
o.config.Joined = false
o.config.Spectated = false
}

func (o *Community) Spectate() {
o.config.Spectated = true
}

func (o *Community) Encrypted() bool {
Expand All @@ -830,6 +838,10 @@ func (o *Community) Joined() bool {
return o.config.Joined
}

func (o *Community) Spectated() bool {
return o.config.Spectated
}

func (o *Community) Verified() bool {
return o.config.Verified
}
Expand Down Expand Up @@ -865,8 +877,8 @@ func (o *Community) UpdateCommunityDescription(signer *ecdsa.PublicKey, descript
return response, nil
}

// We only calculate changes if we joined the community or we requested access, otherwise not interested
if o.config.Joined || o.config.RequestedToJoinAt > 0 {
// We only calculate changes if we joined/spectated the community or we requested access, otherwise not interested
if o.config.Joined || o.config.Spectated || o.config.RequestedToJoinAt > 0 {
// Check for new members at the org level
for pk, member := range description.Members {
if _, ok := o.config.CommunityDescription.Members[pk]; !ok {
Expand Down
19 changes: 19 additions & 0 deletions protocol/communities/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ func (m *Manager) Joined() ([]*Community, error) {
return m.persistence.JoinedCommunities(m.identity)
}

func (m *Manager) Spectated() ([]*Community, error) {
return m.persistence.SpectatedCommunities(m.identity)
}

func (m *Manager) JoinedAndPendingCommunitiesWithRequests() ([]*Community, error) {
return m.persistence.JoinedAndPendingCommunitiesWithRequests(m.identity)
}
Expand Down Expand Up @@ -1023,6 +1027,21 @@ func (m *Manager) JoinCommunity(id types.HexBytes) (*Community, error) {
return community, nil
}

func (m *Manager) SpectateCommunity(id types.HexBytes) (*Community, error) {
community, err := m.GetByID(id)
if err != nil {
return nil, err
}
if community == nil {
return nil, ErrOrgNotFound
}
community.Spectate()
if err = m.persistence.SaveCommunity(community); err != nil {
return nil, err
}
return community, nil
}

func (m *Manager) GetMagnetlinkMessageClock(communityID types.HexBytes) (uint64, error) {
return m.persistence.GetMagnetlinkMessageClock(communityID)
}
Expand Down
33 changes: 20 additions & 13 deletions protocol/communities/persistence.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var ErrOldRequestToJoin = errors.New("old request to join")
var ErrOldRequestToLeave = errors.New("old request to leave")

const OR = " OR "
const communitiesBaseQuery = `SELECT c.id, c.private_key, c.description,c.joined,c.verified,c.muted,r.clock FROM communities_communities c LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?`
const communitiesBaseQuery = `SELECT c.id, c.private_key, c.description,c.joined,c.spectated,c.verified,c.muted,r.clock FROM communities_communities c LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?`

func (p *Persistence) SaveCommunity(community *Community) error {
id := community.ID()
Expand All @@ -35,7 +35,7 @@ func (p *Persistence) SaveCommunity(community *Community) error {
return err
}

_, err = p.db.Exec(`INSERT INTO communities_communities (id, private_key, description, joined, verified) VALUES (?, ?, ?, ?, ?)`, id, crypto.FromECDSA(privateKey), description, community.config.Joined, community.config.Verified)
_, err = p.db.Exec(`INSERT INTO communities_communities (id, private_key, description, joined, spectated, verified) VALUES (?, ?, ?, ?, ?, ?)`, id, crypto.FromECDSA(privateKey), description, community.config.Joined, community.config.Spectated, community.config.Verified)
return err
}

Expand Down Expand Up @@ -95,14 +95,14 @@ func (p *Persistence) queryCommunities(memberIdentity *ecdsa.PublicKey, query st

for rows.Next() {
var publicKeyBytes, privateKeyBytes, descriptionBytes []byte
var joined, verified, muted bool
var joined, spectated, verified, muted bool
var requestedToJoinAt sql.NullInt64
err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &verified, &muted, &requestedToJoinAt)
err := rows.Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &requestedToJoinAt)
if err != nil {
return nil, err
}

org, err := unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, verified, muted, uint64(requestedToJoinAt.Int64), p.logger)
org, err := unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, uint64(requestedToJoinAt.Int64), p.logger)
if err != nil {
return nil, err
}
Expand All @@ -122,6 +122,11 @@ func (p *Persistence) JoinedCommunities(memberIdentity *ecdsa.PublicKey) ([]*Com
return p.queryCommunities(memberIdentity, query)
}

func (p *Persistence) SpectatedCommunities(memberIdentity *ecdsa.PublicKey) ([]*Community, error) {
query := communitiesBaseQuery + ` WHERE c.spectated`
return p.queryCommunities(memberIdentity, query)
}

func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *sql.Rows) (comms []*Community, err error) {
defer func() {
if err != nil {
Expand All @@ -138,21 +143,21 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s

// Community specific fields
var publicKeyBytes, privateKeyBytes, descriptionBytes []byte
var joined, verified, muted bool
var joined, spectated, verified, muted bool

// Request to join specific fields
var rtjID, rtjCommunityID []byte
var rtjPublicKey, rtjENSName, rtjChatID sql.NullString
var rtjClock, rtjState sql.NullInt64

err = rows.Scan(
&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &verified, &muted,
&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted,
&rtjID, &rtjPublicKey, &rtjClock, &rtjENSName, &rtjChatID, &rtjCommunityID, &rtjState)
if err != nil {
return nil, err
}

comm, err = unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, verified, muted, uint64(rtjClock.Int64), p.logger)
comm, err = unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, uint64(rtjClock.Int64), p.logger)
if err != nil {
return nil, err
}
Expand All @@ -169,7 +174,7 @@ func (p *Persistence) rowsToCommunities(memberIdentity *ecdsa.PublicKey, rows *s

func (p *Persistence) JoinedAndPendingCommunitiesWithRequests(memberIdentity *ecdsa.PublicKey) (comms []*Community, err error) {
query := `SELECT
c.id, c.private_key, c.description, c.joined, c.verified, c.muted,
c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted,
r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?
Expand All @@ -185,7 +190,7 @@ WHERE c.Joined OR r.state = ?`

func (p *Persistence) DeletedCommunities(memberIdentity *ecdsa.PublicKey) (comms []*Community, err error) {
query := `SELECT
c.id, c.private_key, c.description, c.joined, c.verified, c.muted,
c.id, c.private_key, c.description, c.joined, c.spectated, c.verified, c.muted,
r.id, r.public_key, r.clock, r.ens_name, r.chat_id, r.community_id, r.state
FROM communities_communities c
LEFT JOIN communities_requests_to_join r ON c.id = r.community_id AND r.public_key = ?
Expand All @@ -207,22 +212,23 @@ func (p *Persistence) CreatedCommunities(memberIdentity *ecdsa.PublicKey) ([]*Co
func (p *Persistence) GetByID(memberIdentity *ecdsa.PublicKey, id []byte) (*Community, error) {
var publicKeyBytes, privateKeyBytes, descriptionBytes []byte
var joined bool
var spectated bool
var verified bool
var muted bool
var requestedToJoinAt sql.NullInt64

err := p.db.QueryRow(communitiesBaseQuery+` WHERE c.id = ?`, common.PubkeyToHex(memberIdentity), id).Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &verified, &muted, &requestedToJoinAt)
err := p.db.QueryRow(communitiesBaseQuery+` WHERE c.id = ?`, common.PubkeyToHex(memberIdentity), id).Scan(&publicKeyBytes, &privateKeyBytes, &descriptionBytes, &joined, &spectated, &verified, &muted, &requestedToJoinAt)

if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}

return unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, verified, muted, uint64(requestedToJoinAt.Int64), p.logger)
return unmarshalCommunityFromDB(memberIdentity, publicKeyBytes, privateKeyBytes, descriptionBytes, joined, spectated, verified, muted, uint64(requestedToJoinAt.Int64), p.logger)
}

func unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, privateKeyBytes, descriptionBytes []byte, joined, verified, muted bool, requestedToJoinAt uint64, logger *zap.Logger) (*Community, error) {
func unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, privateKeyBytes, descriptionBytes []byte, joined, spectated, verified, muted bool, requestedToJoinAt uint64, logger *zap.Logger) (*Community, error) {

var privateKey *ecdsa.PrivateKey
var err error
Expand Down Expand Up @@ -263,6 +269,7 @@ func unmarshalCommunityFromDB(memberIdentity *ecdsa.PublicKey, publicKeyBytes, p
Muted: muted,
RequestedToJoinAt: requestedToJoinAt,
Joined: joined,
Spectated: spectated,
}
return New(config)
}
Expand Down
31 changes: 31 additions & 0 deletions protocol/communities/persistence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,36 @@ func (s *PersistenceSuite) SetupTest() {
s.db = &Persistence{db: db}
}

func (s *PersistenceSuite) TestSaveCommunity() {
id, err := crypto.GenerateKey()
s.Require().NoError(err)

// there is one community inserted by default
communities, err := s.db.AllCommunities(&id.PublicKey)
s.Require().NoError(err)
s.Require().Len(communities, 1)

community := Community{
config: &Config{
PrivateKey: id,
ID: &id.PublicKey,
Joined: true,
Spectated: true,
Verified: true,
CommunityDescription: &protobuf.CommunityDescription{},
},
}
s.Require().NoError(s.db.SaveCommunity(&community))

communities, err = s.db.AllCommunities(&id.PublicKey)
s.Require().NoError(err)
s.Require().Len(communities, 2)
s.Equal(types.HexBytes(crypto.CompressPubkey(&id.PublicKey)), communities[1].ID())
s.Equal(true, communities[1].Joined())
s.Equal(true, communities[1].Spectated())
s.Equal(true, communities[1].Verified())
}

func (s *PersistenceSuite) TestShouldHandleSyncCommunity() {
sc := &protobuf.SyncCommunity{
Id: []byte("0x123456"),
Expand Down Expand Up @@ -244,6 +274,7 @@ func (s *PersistenceSuite) TestGetSyncedRawCommunity() {
Description: []byte("this is a description"),
Joined: true,
Verified: true,
Spectated: true,
}

// add a new community to the db
Expand Down
3 changes: 3 additions & 0 deletions protocol/communities/persistence_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type RawCommunityRow struct {
PrivateKey []byte
Description []byte
Joined bool
Spectated bool
Verified bool
SyncedAt uint64
Muted bool
Expand All @@ -22,6 +23,7 @@ func fromSyncCommunityProtobuf(syncCommProto *protobuf.SyncCommunity) RawCommuni
PrivateKey: syncCommProto.PrivateKey,
Description: syncCommProto.Description,
Joined: syncCommProto.Joined,
Spectated: syncCommProto.Spectated,
Verified: syncCommProto.Verified,
SyncedAt: syncCommProto.Clock,
Muted: syncCommProto.Muted,
Expand All @@ -40,6 +42,7 @@ func (p *Persistence) scanRowToStruct(rowScan func(dest ...interface{}) error) (
&rcr.Verified,
&rcr.Muted,
&syncedAt,
&rcr.Spectated,
)
if syncedAt.Valid {
rcr.SyncedAt = uint64(syncedAt.Time.Unix())
Expand Down
1 change: 1 addition & 0 deletions protocol/communities_messenger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1830,6 +1830,7 @@ func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
s.Equal(newCommunity.Verified(), tnc.Verified())
s.Equal(newCommunity.Muted(), tnc.Muted())
s.Equal(newCommunity.Joined(), tnc.Joined())
s.Equal(newCommunity.Spectated(), tnc.Spectated())
s.Equal(newCommunity.IsAdmin(), tnc.IsAdmin())
s.Equal(newCommunity.InvitationOnly(), tnc.InvitationOnly())
}
Expand Down
Loading

0 comments on commit 480aee7

Please sign in to comment.