diff options
-rw-r--r-- | api4/channel.go | 91 | ||||
-rw-r--r-- | api4/channel_test.go | 159 | ||||
-rw-r--r-- | app/channel.go | 5 | ||||
-rw-r--r-- | model/channel.go | 45 | ||||
-rw-r--r-- | model/channel_test.go | 33 | ||||
-rw-r--r-- | model/client4.go | 32 |
6 files changed, 364 insertions, 1 deletions
diff --git a/api4/channel.go b/api4/channel.go index fd33eb882..a19f7d858 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -20,9 +20,12 @@ func InitChannel() { BaseRoutes.Channels.Handle("/members/{user_id:[A-Za-z0-9]+}/view", ApiSessionRequired(viewChannel)).Methods("POST") BaseRoutes.Team.Handle("/channels", ApiSessionRequired(getPublicChannelsForTeam)).Methods("GET") + BaseRoutes.Team.Handle("/channels/search", ApiSessionRequired(searchChannelsForTeam)).Methods("POST") + BaseRoutes.User.Handle("/teams/{team_id:[A-Za-z0-9]+}/channels", ApiSessionRequired(getChannelsForTeamForUser)).Methods("GET") BaseRoutes.Channel.Handle("", ApiSessionRequired(getChannel)).Methods("GET") BaseRoutes.Channel.Handle("", ApiSessionRequired(updateChannel)).Methods("PUT") + BaseRoutes.Channel.Handle("/patch", ApiSessionRequired(patchChannel)).Methods("PUT") BaseRoutes.Channel.Handle("", ApiSessionRequired(deleteChannel)).Methods("DELETE") BaseRoutes.Channel.Handle("/stats", ApiSessionRequired(getChannelStats)).Methods("GET") @@ -141,6 +144,37 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { } } +func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireChannelId() + if c.Err != nil { + return + } + + patch := model.ChannelPatchFromJson(r.Body) + if patch == nil { + c.SetInvalidParam("channel") + return + } + + oldChannel, err := app.GetChannel(c.Params.ChannelId) + if err != nil { + c.Err = err + return + } + + if !CanManageChannel(c, oldChannel) { + return + } + + if rchannel, err := app.PatchChannel(oldChannel, patch); err != nil { + c.Err = err + return + } else { + c.LogAudit("") + w.Write([]byte(rchannel.ToJson())) + } +} + func CanManageChannel(c *Context, channel *model.Channel) bool { if channel.Type == model.CHANNEL_OPEN && !app.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES) { c.SetPermissionError(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES) @@ -288,6 +322,63 @@ func getPublicChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request } } +func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId().RequireTeamId() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + return + } + + if !app.SessionHasPermissionToTeam(c.Session, c.Params.TeamId, model.PERMISSION_VIEW_TEAM) { + c.SetPermissionError(model.PERMISSION_VIEW_TEAM) + return + } + + if channels, err := app.GetChannelsForUser(c.Params.TeamId, c.Params.UserId); err != nil { + c.Err = err + return + } else if HandleEtag(channels.Etag(), "Get Channels", w, r) { + return + } else { + w.Header().Set(model.HEADER_ETAG_SERVER, channels.Etag()) + w.Write([]byte(channels.ToJson())) + } +} + +func searchChannelsForTeam(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireTeamId() + if c.Err != nil { + return + } + + props := model.ChannelSearchFromJson(r.Body) + if props == nil { + c.SetInvalidParam("channel_search") + return + } + + if len(props.Term) == 0 { + c.SetInvalidParam("term") + return + } + + if !app.SessionHasPermissionToTeam(c.Session, c.Params.TeamId, model.PERMISSION_LIST_TEAM_CHANNELS) { + c.SetPermissionError(model.PERMISSION_LIST_TEAM_CHANNELS) + return + } + + if channels, err := app.SearchChannels(c.Params.TeamId, props.Term); err != nil { + c.Err = err + return + } else { + w.Write([]byte(channels.ToJson())) + } +} + func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireChannelId() if c.Err != nil { diff --git a/api4/channel_test.go b/api4/channel_test.go index ef0d35e4b..a208313df 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -257,6 +257,62 @@ func TestUpdateChannel(t *testing.T) { } +func TestPatchChannel(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer TearDown() + Client := th.Client + + patch := &model.ChannelPatch{ + Name: new(string), + DisplayName: new(string), + Header: new(string), + Purpose: new(string), + } + *patch.Name = model.NewId() + *patch.DisplayName = model.NewId() + *patch.Header = model.NewId() + *patch.Purpose = model.NewId() + + channel, resp := Client.PatchChannel(th.BasicChannel.Id, patch) + CheckNoError(t, resp) + + if *patch.Name != channel.Name { + t.Fatal("do not match") + } else if *patch.DisplayName != channel.DisplayName { + t.Fatal("do not match") + } else if *patch.Header != channel.Header { + t.Fatal("do not match") + } else if *patch.Purpose != channel.Purpose { + t.Fatal("do not match") + } + + patch.Name = nil + oldName := channel.Name + channel, resp = Client.PatchChannel(th.BasicChannel.Id, patch) + CheckNoError(t, resp) + + if channel.Name != oldName { + t.Fatal("should not have updated") + } + + _, resp = Client.PatchChannel("junk", patch) + CheckBadRequestStatus(t, resp) + + _, resp = Client.PatchChannel(model.NewId(), patch) + CheckNotFoundStatus(t, resp) + + user := th.CreateUser() + Client.Login(user.Email, user.Password) + _, resp = Client.PatchChannel(th.BasicChannel.Id, patch) + CheckForbiddenStatus(t, resp) + + _, resp = th.SystemAdminClient.PatchChannel(th.BasicChannel.Id, patch) + CheckNoError(t, resp) + + _, resp = th.SystemAdminClient.PatchChannel(th.BasicPrivateChannel.Id, patch) + CheckNoError(t, resp) +} + func TestCreateDirectChannel(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer TearDown() @@ -438,6 +494,109 @@ func TestGetPublicChannelsForTeam(t *testing.T) { CheckNoError(t, resp) } +func TestGetChannelsForTeamForUser(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer TearDown() + Client := th.Client + + channels, resp := Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "") + CheckNoError(t, resp) + + found := make([]bool, 3) + for _, c := range *channels { + if c.Id == th.BasicChannel.Id { + found[0] = true + } else if c.Id == th.BasicChannel2.Id { + found[1] = true + } else if c.Id == th.BasicPrivateChannel.Id { + found[2] = true + } + + if c.TeamId != th.BasicTeam.Id && c.TeamId != "" { + t.Fatal("wrong team") + } + } + + for _, f := range found { + if !f { + t.Fatal("missing a channel") + } + } + + channels, resp = Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, resp.Etag) + CheckEtag(t, channels, resp) + + _, resp = Client.GetChannelsForTeamForUser(th.BasicTeam.Id, "junk", "") + CheckBadRequestStatus(t, resp) + + _, resp = Client.GetChannelsForTeamForUser("junk", th.BasicUser.Id, "") + CheckBadRequestStatus(t, resp) + + _, resp = Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser2.Id, "") + CheckForbiddenStatus(t, resp) + + _, resp = Client.GetChannelsForTeamForUser(model.NewId(), th.BasicUser.Id, "") + CheckForbiddenStatus(t, resp) + + _, resp = th.SystemAdminClient.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "") + CheckNoError(t, resp) +} + +func TestSearchChannels(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer TearDown() + Client := th.Client + + search := &model.ChannelSearch{Term: th.BasicChannel.Name} + + channels, resp := Client.SearchChannels(th.BasicTeam.Id, search) + CheckNoError(t, resp) + + found := false + for _, c := range *channels { + if c.Type != model.CHANNEL_OPEN { + t.Fatal("should only return public channels") + } + + if c.Id == th.BasicChannel.Id { + found = true + } + } + + if !found { + t.Fatal("didn't find channel") + } + + search.Term = th.BasicPrivateChannel.Name + channels, resp = Client.SearchChannels(th.BasicTeam.Id, search) + CheckNoError(t, resp) + + found = false + for _, c := range *channels { + if c.Id == th.BasicPrivateChannel.Id { + found = true + } + } + + if found { + t.Fatal("shouldn't find private channel") + } + + search.Term = "" + _, resp = Client.SearchChannels(th.BasicTeam.Id, search) + CheckBadRequestStatus(t, resp) + + search.Term = th.BasicChannel.Name + _, resp = Client.SearchChannels(model.NewId(), search) + CheckForbiddenStatus(t, resp) + + _, resp = Client.SearchChannels("junk", search) + CheckBadRequestStatus(t, resp) + + _, resp = th.SystemAdminClient.SearchChannels(th.BasicTeam.Id, search) + CheckNoError(t, resp) +} + func TestDeleteChannel(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer TearDown() diff --git a/app/channel.go b/app/channel.go index d66624f2c..51688ad87 100644 --- a/app/channel.go +++ b/app/channel.go @@ -260,6 +260,11 @@ func UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) { } } +func PatchChannel(channel *model.Channel, patch *model.ChannelPatch) (*model.Channel, *model.AppError) { + channel.Patch(patch) + return UpdateChannel(channel) +} + func UpdateChannelMemberRoles(channelId string, userId string, newRoles string) (*model.ChannelMember, *model.AppError) { var member *model.ChannelMember var err *model.AppError diff --git a/model/channel.go b/model/channel.go index d24fdb2b4..d80674444 100644 --- a/model/channel.go +++ b/model/channel.go @@ -46,6 +46,13 @@ type Channel struct { CreatorId string `json:"creator_id"` } +type ChannelPatch struct { + DisplayName *string `json:"display_name"` + Name *string `json:"name"` + Header *string `json:"header"` + Purpose *string `json:"purpose"` +} + func (o *Channel) ToJson() string { b, err := json.Marshal(o) if err != nil { @@ -55,6 +62,15 @@ func (o *Channel) ToJson() string { } } +func (o *ChannelPatch) ToJson() string { + b, err := json.Marshal(o) + if err != nil { + return "" + } else { + return string(b) + } +} + func ChannelFromJson(data io.Reader) *Channel { decoder := json.NewDecoder(data) var o Channel @@ -66,6 +82,17 @@ func ChannelFromJson(data io.Reader) *Channel { } } +func ChannelPatchFromJson(data io.Reader) *ChannelPatch { + decoder := json.NewDecoder(data) + var o ChannelPatch + err := decoder.Decode(&o) + if err == nil { + return &o + } else { + return nil + } +} + func (o *Channel) Etag() string { return Etag(o.Id, o.UpdateAt) } @@ -137,6 +164,24 @@ func (o *Channel) IsGroupOrDirect() bool { return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP } +func (o *Channel) Patch(patch *ChannelPatch) { + if patch.DisplayName != nil { + o.DisplayName = *patch.DisplayName + } + + if patch.Name != nil { + o.Name = *patch.Name + } + + if patch.Header != nil { + o.Header = *patch.Header + } + + if patch.Purpose != nil { + o.Purpose = *patch.Purpose + } +} + func GetDMNameFromIds(userId1, userId2 string) string { if userId1 > userId2 { return userId2 + "__" + userId1 diff --git a/model/channel_test.go b/model/channel_test.go index deb36633c..207ce4639 100644 --- a/model/channel_test.go +++ b/model/channel_test.go @@ -16,6 +16,39 @@ func TestChannelJson(t *testing.T) { if o.Id != ro.Id { t.Fatal("Ids do not match") } + + p := ChannelPatch{Name: new(string)} + *p.Name = NewId() + json = p.ToJson() + rp := ChannelPatchFromJson(strings.NewReader(json)) + + if *p.Name != *rp.Name { + t.Fatal("names do not match") + } +} + +func TestChannelPatch(t *testing.T) { + p := &ChannelPatch{Name: new(string), DisplayName: new(string), Header: new(string), Purpose: new(string)} + *p.Name = NewId() + *p.DisplayName = NewId() + *p.Header = NewId() + *p.Purpose = NewId() + + o := Channel{Id: NewId(), Name: NewId()} + o.Patch(p) + + if *p.Name != o.Name { + t.Fatal("do not match") + } + if *p.DisplayName != o.DisplayName { + t.Fatal("do not match") + } + if *p.Header != o.Header { + t.Fatal("do not match") + } + if *p.Purpose != o.Purpose { + t.Fatal("do not match") + } } func TestChannelIsValid(t *testing.T) { diff --git a/model/client4.go b/model/client4.go index b269f2f34..c567ab755 100644 --- a/model/client4.go +++ b/model/client4.go @@ -897,6 +897,16 @@ func (c *Client4) UpdateChannel(channel *Channel) (*Channel, *Response) { } } +// PatchChannel partially updates a channel. Any missing fields are not updated. +func (c *Client4) PatchChannel(channelId string, patch *ChannelPatch) (*Channel, *Response) { + if r, err := c.DoApiPut(c.GetChannelRoute(channelId)+"/patch", patch.ToJson()); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + // CreateDirectChannel creates a direct message channel based on the two user // ids provided. func (c *Client4) CreateDirectChannel(userId1, userId2 string) (*Channel, *Response) { @@ -929,7 +939,7 @@ func (c *Client4) GetChannelStats(channelId string, etag string) (*ChannelStats, } } -// GetPublicChannelsForTeam returns a channel based on the provided team id string. +// GetPublicChannelsForTeam returns a list of public channels based on the provided team id string. func (c *Client4) GetPublicChannelsForTeam(teamId string, page int, perPage int, etag string) (*ChannelList, *Response) { query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) if r, err := c.DoApiGet(c.GetPublicChannelsForTeamRoute(teamId)+query, etag); err != nil { @@ -940,6 +950,26 @@ func (c *Client4) GetPublicChannelsForTeam(teamId string, page int, perPage int, } } +// GetChannelsForTeamForUser returns a list channels of on a team for a user. +func (c *Client4) GetChannelsForTeamForUser(teamId, userId, etag string) (*ChannelList, *Response) { + if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetTeamRoute(teamId)+"/channels", etag); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return ChannelListFromJson(r.Body), BuildResponse(r) + } +} + +// SearchChannels returns the channels on a team matching the provided search term. +func (c *Client4) SearchChannels(teamId string, search *ChannelSearch) (*ChannelList, *Response) { + if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/channels/search", search.ToJson()); err != nil { + return nil, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return ChannelListFromJson(r.Body), BuildResponse(r) + } +} + // DeleteChannel deletes channel based on the provided channel id string. func (c *Client4) DeleteChannel(channelId string) (bool, *Response) { if r, err := c.DoApiDelete(c.GetChannelRoute(channelId)); err != nil { |