MM-8678: add CUD support for channel members via plugins (#8565)

* add CUD support for channel members via plugins

This effectively exposes AddChannelMember, UpdateChannelMemberRoles,
UpdateChannelMemberNotifyProps and LeaveChannel via the plugin API.

It also modifies the semantics of AddChannelMember to explicitly allow
for an empty user requestor, left as such for now via the plugin API.

* change the signature of AddChannelMember to accept a channel id instead of a channel
Этот коммит содержится в:
Jesse Hallam
2018-04-06 17:08:57 -04:00
коммит произвёл Joram Wilander
родитель ff077c6761
Коммит 116849842b
7 изменённых файлов: 251 добавлений и 6 удалений

Просмотреть файл

@@ -652,16 +652,18 @@ func (a *App) AddChannelMember(userId string, channel *model.Channel, userReques
} }
var userRequestor *model.User var userRequestor *model.User
if userRequestorId != "" {
if userRequestor, err = a.GetUser(userRequestorId); err != nil { if userRequestor, err = a.GetUser(userRequestorId); err != nil {
return nil, err return nil, err
} }
}
cm, err := a.AddUserToChannel(user, channel) cm, err := a.AddUserToChannel(user, channel)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if userId == userRequestorId { if userRequestorId == "" || userId == userRequestorId {
a.postJoinChannelMessage(user, channel) a.postJoinChannelMessage(user, channel)
} else { } else {
a.Go(func() { a.Go(func() {
@@ -669,7 +671,9 @@ func (a *App) AddChannelMember(userId string, channel *model.Channel, userReques
}) })
} }
if userRequestor != nil {
a.UpdateChannelLastViewedAt([]string{channel.Id}, userRequestor.Id) a.UpdateChannelLastViewedAt([]string{channel.Id}, userRequestor.Id)
}
return cm, nil return cm, nil
} }

Просмотреть файл

@@ -340,3 +340,44 @@ func TestRemoveUserFromChannelUpdatesChannelMemberHistoryRecord(t *testing.T) {
assert.Equal(t, publicChannel.Id, histories[0].ChannelId) assert.Equal(t, publicChannel.Id, histories[0].ChannelId)
assert.NotNil(t, histories[0].LeaveTime) assert.NotNil(t, histories[0].LeaveTime)
} }
func TestAddChannelMemberNoUserRequestor(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
// create a user and add it to a channel
user := th.CreateUser()
if _, err := th.App.AddTeamMember(th.BasicTeam.Id, user.Id); err != nil {
t.Fatal("Failed to add user to team. Error: " + err.Message)
}
groupUserIds := make([]string, 0)
groupUserIds = append(groupUserIds, th.BasicUser.Id)
groupUserIds = append(groupUserIds, user.Id)
channel := th.createChannel(th.BasicTeam, model.CHANNEL_OPEN)
userRequestorId := ""
postRootId := ""
if _, err := th.App.AddChannelMember(user.Id, channel, userRequestorId, postRootId); err != nil {
t.Fatal("Failed to add user to channel. Error: " + err.Message)
}
// there should be a ChannelMemberHistory record for the user
histories := store.Must(th.App.Srv.Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, channel.Id)).([]*model.ChannelMemberHistoryResult)
assert.Len(t, histories, 2)
channelMemberHistoryUserIds := make([]string, 0)
for _, history := range histories {
assert.Equal(t, channel.Id, history.ChannelId)
channelMemberHistoryUserIds = append(channelMemberHistoryUserIds, history.UserId)
}
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
postList := store.Must(th.App.Srv.Store.Post().GetPosts(channel.Id, 0, 1, false)).(*model.PostList)
if assert.Len(t, postList.Order, 1) {
post := postList.Posts[postList.Order[0]]
assert.Equal(t, model.POST_JOIN_CHANNEL, post.Type)
assert.Equal(t, user.Id, post.UserId)
assert.Equal(t, user.Username, post.Props["username"])
}
}

Просмотреть файл

@@ -124,10 +124,35 @@ func (api *PluginAPI) UpdateChannel(channel *model.Channel) (*model.Channel, *mo
return api.app.UpdateChannel(channel) return api.app.UpdateChannel(channel)
} }
func (api *PluginAPI) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
// For now, don't allow overriding these via the plugin API.
userRequestorId := ""
postRootId := ""
channel, err := api.GetChannel(channelId)
if err != nil {
return nil, err
}
return api.app.AddChannelMember(userId, channel, userRequestorId, postRootId)
}
func (api *PluginAPI) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) { func (api *PluginAPI) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
return api.app.GetChannelMember(channelId, userId) return api.app.GetChannelMember(channelId, userId)
} }
func (api *PluginAPI) UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError) {
return api.app.UpdateChannelMemberRoles(channelId, userId, newRoles)
}
func (api *PluginAPI) UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
return api.app.UpdateChannelMemberNotifyProps(notifications, channelId, userId)
}
func (api *PluginAPI) DeleteChannelMember(channelId, userId string) *model.AppError {
return api.app.LeaveChannel(channelId, userId)
}
func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) { func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
return api.app.CreatePostMissingChannel(post, true) return api.app.CreatePostMissingChannel(post, true)
} }

Просмотреть файл

@@ -77,9 +77,21 @@ type API interface {
// UpdateChannel updates a channel. // UpdateChannel updates a channel.
UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError)
// AddChannelMember creates a channel membership for a user.
AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError)
// GetChannelMember gets a channel membership for a user. // GetChannelMember gets a channel membership for a user.
GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError)
// UpdateChannelMemberRoles updates a user's roles for a channel.
UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError)
// UpdateChannelMemberNotifications updates a user's notification properties for a channel.
UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError)
// DeleteChannelMember deletes a channel membership for a user.
DeleteChannelMember(channelId, userId string) *model.AppError
// CreatePost creates a post. // CreatePost creates a post.
CreatePost(post *model.Post) (*model.Post, *model.AppError) CreatePost(post *model.Post) (*model.Post, *model.AppError)

Просмотреть файл

@@ -223,6 +223,16 @@ func (m *API) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppE
return channelOut, err return channelOut, err
} }
func (m *API) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
ret := m.Called(channelId, userId)
if f, ok := ret.Get(0).(func(_, _ string) (*model.ChannelMember, *model.AppError)); ok {
return f(channelId, userId)
}
member, _ := ret.Get(0).(*model.ChannelMember)
err, _ := ret.Get(1).(*model.AppError)
return member, err
}
func (m *API) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) { func (m *API) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
ret := m.Called(channelId, userId) ret := m.Called(channelId, userId)
if f, ok := ret.Get(0).(func(_, _ string) (*model.ChannelMember, *model.AppError)); ok { if f, ok := ret.Get(0).(func(_, _ string) (*model.ChannelMember, *model.AppError)); ok {
@@ -233,6 +243,35 @@ func (m *API) GetChannelMember(channelId, userId string) (*model.ChannelMember,
return member, err return member, err
} }
func (m *API) UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError) {
ret := m.Called(channelId, userId, newRoles)
if f, ok := ret.Get(0).(func(_, _, _ string) (*model.ChannelMember, *model.AppError)); ok {
return f(channelId, userId, newRoles)
}
member, _ := ret.Get(0).(*model.ChannelMember)
err, _ := ret.Get(1).(*model.AppError)
return member, err
}
func (m *API) UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
ret := m.Called(channelId, userId, notifications)
if f, ok := ret.Get(0).(func(_, _ string, _ map[string]string) (*model.ChannelMember, *model.AppError)); ok {
return f(channelId, userId, notifications)
}
member, _ := ret.Get(0).(*model.ChannelMember)
err, _ := ret.Get(1).(*model.AppError)
return member, err
}
func (m *API) DeleteChannelMember(channelId, userId string) *model.AppError {
ret := m.Called(channelId, userId)
if f, ok := ret.Get(0).(func(_, _ string) *model.AppError); ok {
return f(channelId, userId)
}
err, _ := ret.Get(0).(*model.AppError)
return err
}
func (m *API) CreatePost(post *model.Post) (*model.Post, *model.AppError) { func (m *API) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
ret := m.Called(post) ret := m.Called(post)
if f, ok := ret.Get(0).(func(*model.Post) (*model.Post, *model.AppError)); ok { if f, ok := ret.Get(0).(func(*model.Post) (*model.Post, *model.AppError)); ok {

Просмотреть файл

@@ -163,11 +163,33 @@ type APIGetGroupChannelArgs struct {
UserIds []string UserIds []string
} }
type APIAddChannelMemberArgs struct {
ChannelId string
UserId string
}
type APIGetChannelMemberArgs struct { type APIGetChannelMemberArgs struct {
ChannelId string ChannelId string
UserId string UserId string
} }
type APIUpdateChannelMemberRolesArgs struct {
ChannelId string
UserId string
NewRoles string
}
type APIUpdateChannelMemberNotificationsArgs struct {
ChannelId string
UserId string
Notifications map[string]string
}
type APIDeleteChannelMemberArgs struct {
ChannelId string
UserId string
}
type APIChannelReply struct { type APIChannelReply struct {
Channel *model.Channel Channel *model.Channel
Error *model.AppError Error *model.AppError
@@ -239,6 +261,15 @@ func (api *LocalAPI) UpdateChannel(args *model.Channel, reply *APIChannelReply)
return nil return nil
} }
func (api *LocalAPI) AddChannelMember(args *APIAddChannelMemberArgs, reply *APIChannelMemberReply) error {
member, err := api.api.AddChannelMember(args.ChannelId, args.UserId)
*reply = APIChannelMemberReply{
ChannelMember: member,
Error: err,
}
return nil
}
func (api *LocalAPI) GetChannelMember(args *APIGetChannelMemberArgs, reply *APIChannelMemberReply) error { func (api *LocalAPI) GetChannelMember(args *APIGetChannelMemberArgs, reply *APIChannelMemberReply) error {
member, err := api.api.GetChannelMember(args.ChannelId, args.UserId) member, err := api.api.GetChannelMember(args.ChannelId, args.UserId)
*reply = APIChannelMemberReply{ *reply = APIChannelMemberReply{
@@ -248,6 +279,32 @@ func (api *LocalAPI) GetChannelMember(args *APIGetChannelMemberArgs, reply *APIC
return nil return nil
} }
func (api *LocalAPI) UpdateChannelMemberRoles(args *APIUpdateChannelMemberRolesArgs, reply *APIChannelMemberReply) error {
member, err := api.api.UpdateChannelMemberRoles(args.ChannelId, args.UserId, args.NewRoles)
*reply = APIChannelMemberReply{
ChannelMember: member,
Error: err,
}
return nil
}
func (api *LocalAPI) UpdateChannelMemberNotifications(args *APIUpdateChannelMemberNotificationsArgs, reply *APIChannelMemberReply) error {
member, err := api.api.UpdateChannelMemberNotifications(args.ChannelId, args.UserId, args.Notifications)
*reply = APIChannelMemberReply{
ChannelMember: member,
Error: err,
}
return nil
}
func (api *LocalAPI) DeleteChannelMember(args *APIDeleteChannelMemberArgs, reply *APIErrorReply) error {
err := api.api.DeleteChannelMember(args.ChannelId, args.UserId)
*reply = APIErrorReply{
Error: err,
}
return nil
}
type APIPostReply struct { type APIPostReply struct {
Post *model.Post Post *model.Post
Error *model.AppError Error *model.AppError
@@ -520,6 +577,17 @@ func (api *RemoteAPI) UpdateChannel(channel *model.Channel) (*model.Channel, *mo
return reply.Channel, reply.Error return reply.Channel, reply.Error
} }
func (api *RemoteAPI) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
var reply APIChannelMemberReply
if err := api.client.Call("LocalAPI.AddChannelMember", &APIAddChannelMemberArgs{
ChannelId: channelId,
UserId: userId,
}, &reply); err != nil {
return nil, model.NewAppError("RemoteAPI.AddChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
return reply.ChannelMember, reply.Error
}
func (api *RemoteAPI) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) { func (api *RemoteAPI) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
var reply APIChannelMemberReply var reply APIChannelMemberReply
if err := api.client.Call("LocalAPI.GetChannelMember", &APIGetChannelMemberArgs{ if err := api.client.Call("LocalAPI.GetChannelMember", &APIGetChannelMemberArgs{
@@ -531,6 +599,41 @@ func (api *RemoteAPI) GetChannelMember(channelId, userId string) (*model.Channel
return reply.ChannelMember, reply.Error return reply.ChannelMember, reply.Error
} }
func (api *RemoteAPI) UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError) {
var reply APIChannelMemberReply
if err := api.client.Call("LocalAPI.UpdateChannelMemberRoles", &APIUpdateChannelMemberRolesArgs{
ChannelId: channelId,
UserId: userId,
NewRoles: newRoles,
}, &reply); err != nil {
return nil, model.NewAppError("RemoteAPI.UpdateChannelMemberRoles", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
return reply.ChannelMember, reply.Error
}
func (api *RemoteAPI) UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
var reply APIChannelMemberReply
if err := api.client.Call("LocalAPI.UpdateChannelMemberNotifications", &APIUpdateChannelMemberNotificationsArgs{
ChannelId: channelId,
UserId: userId,
Notifications: notifications,
}, &reply); err != nil {
return nil, model.NewAppError("RemoteAPI.UpdateChannelMemberNotifications", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
return reply.ChannelMember, reply.Error
}
func (api *RemoteAPI) DeleteChannelMember(channelId, userId string) *model.AppError {
var reply APIErrorReply
if err := api.client.Call("LocalAPI.DeleteChannelMember", &APIDeleteChannelMemberArgs{
ChannelId: channelId,
UserId: userId,
}, &reply); err != nil {
return model.NewAppError("RemoteAPI.DeleteChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
return reply.Error
}
func (api *RemoteAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) { func (api *RemoteAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
var reply APIPostReply var reply APIPostReply
if err := api.client.Call("LocalAPI.CreatePost", post, &reply); err != nil { if err := api.client.Call("LocalAPI.CreatePost", post, &reply); err != nil {

Просмотреть файл

@@ -128,11 +128,32 @@ func TestAPI(t *testing.T) {
assert.Equal(t, testChannel, channel) assert.Equal(t, testChannel, channel)
assert.Nil(t, err) assert.Nil(t, err)
api.On("GetChannelMember", "thechannelid", "theuserid").Return(testChannelMember, nil).Once() api.On("AddChannelMember", testChannel.Id, "theuserid").Return(testChannelMember, nil).Once()
member, err := remote.GetChannelMember("thechannelid", "theuserid") member, err := remote.AddChannelMember(testChannel.Id, "theuserid")
assert.Equal(t, testChannelMember, member) assert.Equal(t, testChannelMember, member)
assert.Nil(t, err) assert.Nil(t, err)
api.On("GetChannelMember", "thechannelid", "theuserid").Return(testChannelMember, nil).Once()
member, err = remote.GetChannelMember("thechannelid", "theuserid")
assert.Equal(t, testChannelMember, member)
assert.Nil(t, err)
api.On("UpdateChannelMemberRoles", testChannel.Id, "theuserid", model.CHANNEL_ADMIN_ROLE_ID).Return(testChannelMember, nil).Once()
member, err = remote.UpdateChannelMemberRoles(testChannel.Id, "theuserid", model.CHANNEL_ADMIN_ROLE_ID)
assert.Equal(t, testChannelMember, member)
assert.Nil(t, err)
notifications := map[string]string{}
notifications[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION
api.On("UpdateChannelMemberNotifications", testChannel.Id, "theuserid", notifications).Return(testChannelMember, nil).Once()
member, err = remote.UpdateChannelMemberNotifications(testChannel.Id, "theuserid", notifications)
assert.Equal(t, testChannelMember, member)
assert.Nil(t, err)
api.On("DeleteChannelMember", "thechannelid", "theuserid").Return(nil).Once()
err = remote.DeleteChannelMember("thechannelid", "theuserid")
assert.Nil(t, err)
api.On("CreateUser", mock.AnythingOfType("*model.User")).Return(func(u *model.User) (*model.User, *model.AppError) { api.On("CreateUser", mock.AnythingOfType("*model.User")).Return(func(u *model.User) (*model.User, *model.AppError) {
u.Id = "theuserid" u.Id = "theuserid"
return u, nil return u, nil