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:
parent
dd49e604d4
commit
4d8d3fb0e8
|
@ -490,6 +490,14 @@ func (o *Community) BanUserFromCommunity(pk *ecdsa.PublicKey) (*protobuf.Communi
|
||||||
return o.config.CommunityDescription, nil
|
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() {
|
func (o *Community) Join() {
|
||||||
o.config.Joined = true
|
o.config.Joined = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package communities
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"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
|
// CreateCommunity takes a description, generates an ID for it, saves it and return it
|
||||||
func (m *Manager) CreateCommunity(description *protobuf.CommunityDescription) (*Community, error) {
|
func (m *Manager) CreateCommunity(request *requests.CreateCommunity) (*Community, error) {
|
||||||
err := ValidateCommunityDescription(description)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -173,13 +183,65 @@ func (m *Manager) CreateCommunity(description *protobuf.CommunityDescription) (*
|
||||||
return community, nil
|
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) {
|
func (m *Manager) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error) {
|
||||||
community, err := m.GetByID(id)
|
community, err := m.GetByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if community.config.PrivateKey == nil {
|
if !community.IsAdmin() {
|
||||||
return nil, errors.New("not an admin")
|
return nil, errors.New("not an admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/protocol/requests"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
_ "github.com/mutecomm/go-sqlcipher" // require go-sqlcipher that overrides default implementation
|
_ "github.com/mutecomm/go-sqlcipher" // require go-sqlcipher that overrides default implementation
|
||||||
|
|
||||||
|
@ -36,17 +38,14 @@ func (s *ManagerSuite) SetupTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ManagerSuite) TestCreateCommunity() {
|
func (s *ManagerSuite) TestCreateCommunity() {
|
||||||
description := &protobuf.CommunityDescription{
|
|
||||||
Permissions: &protobuf.CommunityPermissions{
|
request := &requests.CreateCommunity{
|
||||||
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
|
Name: "status",
|
||||||
},
|
Description: "status community description",
|
||||||
Identity: &protobuf.ChatIdentity{
|
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
|
||||||
DisplayName: "status",
|
|
||||||
Description: "status community description",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
community, err := s.manager.CreateCommunity(description)
|
community, err := s.manager.CreateCommunity(request)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(community)
|
s.Require().NotNil(community)
|
||||||
|
|
||||||
|
@ -64,3 +63,44 @@ func (s *ManagerSuite) TestCreateCommunity() {
|
||||||
s.Require().Equal(community.PrivateKey(), actualCommunity.PrivateKey())
|
s.Require().Equal(community.PrivateKey(), actualCommunity.PrivateKey())
|
||||||
s.Require().True(proto.Equal(community.config.CommunityDescription, actualCommunity.config.CommunityDescription))
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -329,15 +329,7 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
description, err := request.ToCommunityDescription()
|
community, err := m.communitiesManager.CreateCommunity(request)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -357,6 +349,22 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
|
||||||
return response, nil
|
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) {
|
func (m *Messenger) ExportCommunity(id types.HexBytes) (*ecdsa.PrivateKey, error) {
|
||||||
return m.communitiesManager.ExportCommunity(id)
|
return m.communitiesManager.ExportCommunity(id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -345,7 +345,11 @@ func (api *PublicAPI) LeaveCommunity(parent context.Context, communityID types.H
|
||||||
// CreateCommunity creates a new community with the provided description
|
// CreateCommunity creates a new community with the provided description
|
||||||
func (api *PublicAPI) CreateCommunity(request *requests.CreateCommunity) (*protocol.MessengerResponse, error) {
|
func (api *PublicAPI) CreateCommunity(request *requests.CreateCommunity) (*protocol.MessengerResponse, error) {
|
||||||
return api.service.messenger.CreateCommunity(request)
|
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
|
// ExportCommunity exports the private key of the community with given ID
|
||||||
|
|
Loading…
Reference in New Issue