feat: Add edit communities (#2229)

* feat: Add edit communities

Allow Communities to be edited, including display name, description, color, membership, and permissions.

* Added EditCommunity request type

* Fix lint errors

* Allow editing community without changing image

Previously, retaining an existing community image was not possible because the existing community image path had to be provided in the `editCommunity` RPC call to retain the image. However, once the image is processed by status-go, it is encoded as a base64 string and therefore it is not possible to get the original file path back from this string.

This commit allows for the original to be retained by passing an empty string for the image field in the RPC call.

* Don't change permissions. Fixed clock updating

Co-authored-by: Volodymyr Kozieiev <vkjr.sp@gmail.com>
This commit is contained in:
Eric Mastro 2021-05-19 05:32:15 +10:00 committed by GitHub
parent dd49e604d4
commit 4d8d3fb0e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 190 additions and 21 deletions

View File

@ -490,6 +490,14 @@ func (o *Community) BanUserFromCommunity(pk *ecdsa.PublicKey) (*protobuf.Communi
return o.config.CommunityDescription, nil
}
func (o *Community) Edit(description *protobuf.CommunityDescription) {
o.config.CommunityDescription.Identity.DisplayName = description.Identity.DisplayName
o.config.CommunityDescription.Identity.Description = description.Identity.Description
o.config.CommunityDescription.Identity.Color = description.Identity.Color
o.config.CommunityDescription.Identity.Images = description.Identity.Images
o.increaseClock()
}
func (o *Community) Join() {
o.config.Joined = true
}

View File

@ -3,6 +3,7 @@ package communities
import (
"crypto/ecdsa"
"database/sql"
"fmt"
"time"
"github.com/golang/protobuf/proto"
@ -134,8 +135,17 @@ func (m *Manager) Created() ([]*Community, error) {
}
// CreateCommunity takes a description, generates an ID for it, saves it and return it
func (m *Manager) CreateCommunity(description *protobuf.CommunityDescription) (*Community, error) {
err := ValidateCommunityDescription(description)
func (m *Manager) CreateCommunity(request *requests.CreateCommunity) (*Community, error) {
description, err := request.ToCommunityDescription()
if err != nil {
return nil, err
}
description.Members = make(map[string]*protobuf.CommunityMember)
description.Members[common.PubkeyToHex(m.identity)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_ALL}}
err = ValidateCommunityDescription(description)
if err != nil {
return nil, err
}
@ -173,13 +183,65 @@ func (m *Manager) CreateCommunity(description *protobuf.CommunityDescription) (*
return community, nil
}
// CreateCommunity takes a description, updates the community with the description,
// saves it and returns it
func (m *Manager) EditCommunity(request *requests.EditCommunity) (*Community, error) {
community, err := m.GetByID(request.CommunityID)
if err != nil {
return nil, err
}
if community == nil {
return nil, ErrOrgNotFound
}
if !community.IsAdmin() {
return nil, errors.New("not an admin")
}
newDescription, err := request.ToCommunityDescription()
if err != nil {
return nil, fmt.Errorf("Can't create community description: %v", err)
}
// If permissions weren't explicitly set on original request, use existing ones
if newDescription.Permissions.Access == protobuf.CommunityPermissions_UNKNOWN_ACCESS {
newDescription.Permissions.Access = community.config.CommunityDescription.Permissions.Access
}
// If the image wasn't edited, use the existing one
// NOTE: This will NOT allow deletion of the community image; it will need to
// be handled separately.
if request.Image == "" {
newDescription.Identity.Images = community.config.CommunityDescription.Identity.Images
}
// TODO: handle delete image (if needed)
err = ValidateCommunityDescription(newDescription)
if err != nil {
return nil, err
}
// Edit the community values
community.Edit(newDescription)
if err != nil {
return nil, err
}
err = m.persistence.SaveCommunity(community)
if err != nil {
return nil, err
}
m.publish(&Subscription{Community: community})
return community, nil
}
func (m *Manager) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error) {
community, err := m.GetByID(id)
if err != nil {
return nil, err
}
if community.config.PrivateKey == nil {
if !community.IsAdmin() {
return nil, errors.New("not an admin")
}

View File

@ -4,6 +4,8 @@ import (
"bytes"
"testing"
"github.com/status-im/status-go/protocol/requests"
"github.com/golang/protobuf/proto"
_ "github.com/mutecomm/go-sqlcipher" // require go-sqlcipher that overrides default implementation
@ -36,17 +38,14 @@ func (s *ManagerSuite) SetupTest() {
}
func (s *ManagerSuite) TestCreateCommunity() {
description := &protobuf.CommunityDescription{
Permissions: &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
},
Identity: &protobuf.ChatIdentity{
DisplayName: "status",
Description: "status community description",
},
request := &requests.CreateCommunity{
Name: "status",
Description: "status community description",
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
}
community, err := s.manager.CreateCommunity(description)
community, err := s.manager.CreateCommunity(request)
s.Require().NoError(err)
s.Require().NotNil(community)
@ -64,3 +63,44 @@ func (s *ManagerSuite) TestCreateCommunity() {
s.Require().Equal(community.PrivateKey(), actualCommunity.PrivateKey())
s.Require().True(proto.Equal(community.config.CommunityDescription, actualCommunity.config.CommunityDescription))
}
func (s *ManagerSuite) TestEditCommunity() {
//create community
createRequest := &requests.CreateCommunity{
Name: "status",
Description: "status community description",
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
}
community, err := s.manager.CreateCommunity(createRequest)
s.Require().NoError(err)
s.Require().NotNil(community)
update := &requests.EditCommunity{
CommunityID: community.ID(),
CreateCommunity: requests.CreateCommunity{
Name: "statusEdited",
Description: "status community description edited",
},
}
updatedCommunity, err := s.manager.EditCommunity(update)
s.Require().NoError(err)
s.Require().NotNil(updatedCommunity)
//ensure updated community successfully stored
communities, err := s.manager.All()
s.Require().NoError(err)
// Consider status default community
s.Require().Len(communities, 2)
storedCommunity := communities[0]
if bytes.Equal(community.ID(), communities[1].ID()) {
storedCommunity = communities[1]
}
s.Require().Equal(storedCommunity.ID(), updatedCommunity.ID())
s.Require().Equal(storedCommunity.PrivateKey(), updatedCommunity.PrivateKey())
s.Require().Equal(storedCommunity.config.CommunityDescription.Identity.DisplayName, update.CreateCommunity.Name)
s.Require().Equal(storedCommunity.config.CommunityDescription.Identity.Description, update.CreateCommunity.Description)
}

View File

@ -329,15 +329,7 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
return nil, err
}
description, err := request.ToCommunityDescription()
if err != nil {
return nil, err
}
description.Members = make(map[string]*protobuf.CommunityMember)
description.Members[common.PubkeyToHex(&m.identity.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_ALL}}
community, err := m.communitiesManager.CreateCommunity(description)
community, err := m.communitiesManager.CreateCommunity(request)
if err != nil {
return nil, err
}
@ -357,6 +349,22 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
return response, nil
}
func (m *Messenger) EditCommunity(request *requests.EditCommunity) (*MessengerResponse, error) {
if err := request.Validate(); err != nil {
return nil, err
}
community, err := m.communitiesManager.EditCommunity(request)
if err != nil {
return nil, err
}
response := &MessengerResponse{}
response.AddCommunity(community)
return response, nil
}
func (m *Messenger) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error) {
return m.communitiesManager.ExportCommunity(id)
}

View File

@ -0,0 +1,47 @@
package requests
import (
"errors"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/protobuf"
)
var (
ErrEditCommunityInvalidID = errors.New("edit-community: invalid id")
ErrEditCommunityInvalidName = errors.New("edit-community: invalid name")
ErrEditCommunityInvalidColor = errors.New("edit-community: invalid color")
ErrEditCommunityInvalidDescription = errors.New("edit-community: invalid description")
ErrEditCommunityInvalidMembership = errors.New("edit-community: invalid membership")
)
type EditCommunity struct {
CommunityID types.HexBytes
CreateCommunity
}
func (u *EditCommunity) Validate() error {
if len(u.CommunityID) == 0 {
return ErrEditCommunityInvalidID
}
if u.Name == "" {
return ErrEditCommunityInvalidName
}
if u.Description == "" {
return ErrEditCommunityInvalidDescription
}
if u.Membership == protobuf.CommunityPermissions_UNKNOWN_ACCESS {
return ErrEditCommunityInvalidMembership
}
if u.Color == "" {
return ErrEditCommunityInvalidColor
}
return nil
}

View File

@ -345,7 +345,11 @@ func (api *PublicAPI) LeaveCommunity(parent context.Context, communityID types.H
// CreateCommunity creates a new community with the provided description
func (api *PublicAPI) CreateCommunity(request *requests.CreateCommunity) (*protocol.MessengerResponse, error) {
return api.service.messenger.CreateCommunity(request)
}
// EditCommunity edits an existing community with the provided description
func (api *PublicAPI) EditCommunity(request *requests.EditCommunity) (*protocol.MessengerResponse, error) {
return api.service.messenger.EditCommunity(request)
}
// ExportCommunity exports the private key of the community with given ID