summaryrefslogtreecommitdiffstats
path: root/model
diff options
context:
space:
mode:
Diffstat (limited to 'model')
-rw-r--r--model/autocomplete.go58
-rw-r--r--model/channel.go4
-rw-r--r--model/channel_extra.go49
-rw-r--r--model/channel_stats.go34
-rw-r--r--model/client.go164
-rw-r--r--model/initial_load.go15
-rw-r--r--model/session.go2
-rw-r--r--model/status.go5
-rw-r--r--model/team_stats.go34
-rw-r--r--model/user.go20
-rw-r--r--model/utils.go30
-rw-r--r--model/utils_test.go7
-rw-r--r--model/websocket_client.go21
-rw-r--r--model/websocket_message.go44
14 files changed, 400 insertions, 87 deletions
diff --git a/model/autocomplete.go b/model/autocomplete.go
new file mode 100644
index 000000000..b7449a792
--- /dev/null
+++ b/model/autocomplete.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type UserAutocompleteInChannel struct {
+ InChannel []*User `json:"in_channel"`
+ OutOfChannel []*User `json:"out_of_channel"`
+}
+
+type UserAutocompleteInTeam struct {
+ InTeam []*User `json:"in_team"`
+}
+
+func (o *UserAutocompleteInChannel) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func UserAutocompleteInChannelFromJson(data io.Reader) *UserAutocompleteInChannel {
+ decoder := json.NewDecoder(data)
+ var o UserAutocompleteInChannel
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *UserAutocompleteInTeam) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func UserAutocompleteInTeamFromJson(data io.Reader) *UserAutocompleteInTeam {
+ decoder := json.NewDecoder(data)
+ var o UserAutocompleteInTeam
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/model/channel.go b/model/channel.go
index 3da9f4feb..7dee079c5 100644
--- a/model/channel.go
+++ b/model/channel.go
@@ -57,8 +57,8 @@ func (o *Channel) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
-func (o *Channel) ExtraEtag(memberLimit int) string {
- return Etag(o.Id, o.ExtraUpdateAt, memberLimit)
+func (o *Channel) StatsEtag() string {
+ return Etag(o.Id, o.ExtraUpdateAt)
}
func (o *Channel) IsValid() *AppError {
diff --git a/model/channel_extra.go b/model/channel_extra.go
deleted file mode 100644
index 55da588af..000000000
--- a/model/channel_extra.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package model
-
-import (
- "encoding/json"
- "io"
-)
-
-type ExtraMember struct {
- Id string `json:"id"`
- Nickname string `json:"nickname"`
- Email string `json:"email"`
- Roles string `json:"roles"`
- Username string `json:"username"`
-}
-
-func (o *ExtraMember) Sanitize(options map[string]bool) {
- if len(options) == 0 || !options["email"] {
- o.Email = ""
- }
-}
-
-type ChannelExtra struct {
- Id string `json:"id"`
- Members []ExtraMember `json:"members"`
- MemberCount int64 `json:"member_count"`
-}
-
-func (o *ChannelExtra) ToJson() string {
- b, err := json.Marshal(o)
- if err != nil {
- return ""
- } else {
- return string(b)
- }
-}
-
-func ChannelExtraFromJson(data io.Reader) *ChannelExtra {
- decoder := json.NewDecoder(data)
- var o ChannelExtra
- err := decoder.Decode(&o)
- if err == nil {
- return &o
- } else {
- return nil
- }
-}
diff --git a/model/channel_stats.go b/model/channel_stats.go
new file mode 100644
index 000000000..079769eb0
--- /dev/null
+++ b/model/channel_stats.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type ChannelStats struct {
+ ChannelId string `json:"channel_id"`
+ MemberCount int64 `json:"member_count"`
+}
+
+func (o *ChannelStats) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelStatsFromJson(data io.Reader) *ChannelStats {
+ decoder := json.NewDecoder(data)
+ var o ChannelStats
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/model/client.go b/model/client.go
index f9a56b86e..f5aeea4db 100644
--- a/model/client.go
+++ b/model/client.go
@@ -131,6 +131,7 @@ func (c *Client) GetFileRoute(fileId string) string {
func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
rq.Header.Set("Content-Type", contentType)
+ rq.Close = true
if rp, err := c.HttpClient.Do(rq); err != nil {
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
@@ -144,6 +145,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro
func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data))
+ rq.Close = true
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
@@ -161,6 +163,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError)
func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data))
+ rq.Close = true
if len(etag) > 0 {
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
@@ -508,10 +511,9 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) {
}
}
-// GetProfilesForDirectMessageList returns a map of users for a team that can be direct
-// messaged, using user id as the key. Must be authenticated.
-func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil {
+// GetProfiles returns a map of users using user id as the key. Must be authenticated.
+func (c *Client) GetProfiles(offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/users/%v/%v", offset, limit), "", etag); err != nil {
return nil, err
} else {
defer closeBody(r)
@@ -520,10 +522,10 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr
}
}
-// GetProfiles returns a map of users for a team using user id as the key. Must
+// GetProfilesInTeam returns a map of users for a team using user id as the key. Must
// be authenticated.
-func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
+func (c *Client) GetProfilesInTeam(teamId string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/users/%v/%v", teamId, offset, limit), "", etag); err != nil {
return nil, err
} else {
defer closeBody(r)
@@ -532,10 +534,22 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
}
}
-// GetDirectProfiles gets a map of users that are currently shown in the sidebar,
-// using user id as the key. Must be authenticated.
-func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil {
+// GetProfilesInChannel returns a map of users for a channel using user id as the key. Must
+// be authenticated.
+func (c *Client) GetProfilesInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/%v/%v", offset, limit), "", etag); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
+ }
+}
+
+// GetProfilesNotInChannel returns a map of users not in a channel but on the team using user id as the key. Must
+// be authenticated.
+func (c *Client) GetProfilesNotInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/not_in_channel/%v/%v", offset, limit), "", etag); err != nil {
return nil, err
} else {
defer closeBody(r)
@@ -544,6 +558,60 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) {
}
}
+// GetProfilesByIds returns a map of users based on the user ids provided. Must
+// be authenticated.
+func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/ids", ArrayToJson(userIds)); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
+ }
+}
+
+// SearchUsers returns a list of users that have a username matching or similar to the search term. Must
+// be authenticated.
+func (c *Client) SearchUsers(term string, teamId string, options map[string]string) (*Result, *AppError) {
+ options["term"] = term
+ options["team_id"] = teamId
+ if r, err := c.DoApiPost("/users/search", MapToJson(options)); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil
+ }
+}
+
+// AutocompleteUsersInChannel returns two lists for autocompletion of users in a channel. The first list "in_channel",
+// specifies users in the channel. The second list "out_of_channel" specifies users outside of the
+// channel. Term, the string to search against, is required, channel id is also required. Must be authenticated.
+func (c *Client) AutocompleteUsersInChannel(term string, channelId string) (*Result, *AppError) {
+ url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetChannelRoute(channelId), url.QueryEscape(term))
+ if r, err := c.DoApiGet(url, "", ""); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInChannelFromJson(r.Body)}, nil
+ }
+}
+
+// AutocompleteUsersInTeam returns a list for autocompletion of users in a team. The list "in_team" specifies
+// the users in the team that match the provided term, matching against username, full name and
+// nickname. Must be authenticated.
+func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) {
+ url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term))
+ if r, err := c.DoApiGet(url, "", ""); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInTeamFromJson(r.Body)}, nil
+ }
+}
+
// LoginById authenticates a user by user id and password.
func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
m := make(map[string]string)
@@ -942,6 +1010,7 @@ func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) {
func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) {
var rq *http.Request
rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil)
+ rq.Close = true
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
@@ -1174,13 +1243,23 @@ func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *Ap
}
}
-func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) {
- if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil {
+func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelStatsFromJson(r.Body)}, nil
+ }
+}
+
+func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/members/"+userId, "", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
- r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelMemberFromJson(r.Body)}, nil
}
}
@@ -1325,6 +1404,7 @@ func (c *Client) UploadPostAttachment(data []byte, channelId string, filename st
func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
rq.Header.Set("Content-Type", contentType)
+ rq.Close = true
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
@@ -1525,6 +1605,18 @@ func (c *Client) GetStatuses() (*Result, *AppError) {
}
}
+// GetStatusesByIds returns a map of string statuses using user id as the key,
+// based on the provided user ids
+func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) {
+ if r, err := c.DoApiPost("/users/status/ids", ArrayToJson(userIds)); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
+ }
+}
+
// SetActiveChannel sets the the channel id the user is currently viewing.
// The channelId key is required but the value can be blank. Returns standard
// response.
@@ -1550,8 +1642,46 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
}
}
-func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) {
- if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil {
+// GetTeamMembers will return a page of team member objects as an array paged based on the
+// team id, offset and limit provided. Must be authenticated.
+func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v/%v", teamId, offset, limit), "", ""); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil
+ }
+}
+
+// GetTeamMember will return a team member object based on the team id and user id provided.
+// Must be authenticated.
+func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v", teamId, userId), "", ""); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamMemberFromJson(r.Body)}, nil
+ }
+}
+
+// GetTeamStats will return a team stats object containing the number of users on the team
+// based on the team id provided. Must be authenticated.
+func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) {
+ if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/stats", teamId), "", ""); err != nil {
+ return nil, err
+ } else {
+ defer closeBody(r)
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil
+ }
+}
+
+// GetTeamMembersByIds will return team member objects as an array based on the
+// team id and a list of user ids provided. Must be authenticated.
+func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) {
+ if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil {
return nil, err
} else {
defer closeBody(r)
@@ -1866,6 +1996,7 @@ func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoj
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body)
rq.Header.Set("Content-Type", writer.FormDataContentType())
+ rq.Close = true
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
@@ -1908,6 +2039,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro
url := c.ApiUrl + "/admin/add_certificate"
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
rq.Header.Set("Content-Type", contentType)
+ rq.Close = true
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
diff --git a/model/initial_load.go b/model/initial_load.go
index d7587e6d4..afb0a2762 100644
--- a/model/initial_load.go
+++ b/model/initial_load.go
@@ -9,14 +9,13 @@ import (
)
type InitialLoad struct {
- User *User `json:"user"`
- TeamMembers []*TeamMember `json:"team_members"`
- Teams []*Team `json:"teams"`
- DirectProfiles map[string]*User `json:"direct_profiles"`
- Preferences Preferences `json:"preferences"`
- ClientCfg map[string]string `json:"client_cfg"`
- LicenseCfg map[string]string `json:"license_cfg"`
- NoAccounts bool `json:"no_accounts"`
+ User *User `json:"user"`
+ TeamMembers []*TeamMember `json:"team_members"`
+ Teams []*Team `json:"teams"`
+ Preferences Preferences `json:"preferences"`
+ ClientCfg map[string]string `json:"client_cfg"`
+ LicenseCfg map[string]string `json:"license_cfg"`
+ NoAccounts bool `json:"no_accounts"`
}
func (me *InitialLoad) ToJson() string {
diff --git a/model/session.go b/model/session.go
index c3171ed7c..a6a753e7f 100644
--- a/model/session.go
+++ b/model/session.go
@@ -11,7 +11,7 @@ import (
const (
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
- SESSION_CACHE_SIZE = 10000
+ SESSION_CACHE_SIZE = 25000
SESSION_PROP_PLATFORM = "platform"
SESSION_PROP_OS = "os"
SESSION_PROP_BROWSER = "browser"
diff --git a/model/status.go b/model/status.go
index f4ad8e775..324866427 100644
--- a/model/status.go
+++ b/model/status.go
@@ -12,8 +12,9 @@ const (
STATUS_OFFLINE = "offline"
STATUS_AWAY = "away"
STATUS_ONLINE = "online"
- STATUS_CACHE_SIZE = 10000
- STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
+ STATUS_CACHE_SIZE = 25000
+ STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
+ STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes
)
type Status struct {
diff --git a/model/team_stats.go b/model/team_stats.go
new file mode 100644
index 000000000..8634c3d1b
--- /dev/null
+++ b/model/team_stats.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type TeamStats struct {
+ TeamId string `json:"team_id"`
+ MemberCount int64 `json:"member_count"`
+}
+
+func (o *TeamStats) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func TeamStatsFromJson(data io.Reader) *TeamStats {
+ decoder := json.NewDecoder(data)
+ var o TeamStats
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/model/user.go b/model/user.go
index 1b51171ad..e713444ba 100644
--- a/model/user.go
+++ b/model/user.go
@@ -413,6 +413,26 @@ func UserMapFromJson(data io.Reader) map[string]*User {
}
}
+func UserListToJson(u []*User) string {
+ b, err := json.Marshal(u)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func UserListFromJson(data io.Reader) []*User {
+ decoder := json.NewDecoder(data)
+ var users []*User
+ err := decoder.Decode(&users)
+ if err == nil {
+ return users
+ } else {
+ return nil
+ }
+}
+
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
func HashPassword(password string) string {
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
diff --git a/model/utils.go b/model/utils.go
index a9c441fee..4ebd23939 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -10,6 +10,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "io/ioutil"
"net/mail"
"net/url"
"regexp"
@@ -74,13 +75,21 @@ func (er *AppError) ToJson() string {
// AppErrorFromJson will decode the input and return an AppError
func AppErrorFromJson(data io.Reader) *AppError {
- decoder := json.NewDecoder(data)
+ str := ""
+ bytes, rerr := ioutil.ReadAll(data)
+ if rerr != nil {
+ str = rerr.Error()
+ } else {
+ str = string(bytes)
+ }
+
+ decoder := json.NewDecoder(strings.NewReader(str))
var er AppError
err := decoder.Decode(&er)
if err == nil {
return &er
} else {
- return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, err.Error())
+ return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str)
}
}
@@ -166,6 +175,23 @@ func ArrayFromJson(data io.Reader) []string {
}
}
+func ArrayFromInterface(data interface{}) []string {
+ stringArray := []string{}
+
+ dataArray, ok := data.([]interface{})
+ if !ok {
+ return stringArray
+ }
+
+ for _, v := range dataArray {
+ if str, ok := v.(string); ok {
+ stringArray = append(stringArray, str)
+ }
+ }
+
+ return stringArray
+}
+
func StringInterfaceToJson(objmap map[string]interface{}) string {
if b, err := json.Marshal(objmap); err != nil {
return ""
diff --git a/model/utils_test.go b/model/utils_test.go
index 5d9289a9a..dbe1e59be 100644
--- a/model/utils_test.go
+++ b/model/utils_test.go
@@ -37,6 +37,13 @@ func TestAppError(t *testing.T) {
err.Error()
}
+func TestAppErrorJunk(t *testing.T) {
+ rerr := AppErrorFromJson(strings.NewReader("<html><body>This is a broken test</body></html>"))
+ if "body: <html><body>This is a broken test</body></html>" != rerr.DetailedError {
+ t.Fatal()
+ }
+}
+
func TestMapJson(t *testing.T) {
m := make(map[string]string)
diff --git a/model/websocket_client.go b/model/websocket_client.go
index a048bd855..a4983e385 100644
--- a/model/websocket_client.go
+++ b/model/websocket_client.go
@@ -17,6 +17,7 @@ type WebSocketClient struct {
Sequence int64 // The ever-incrementing sequence attached to each WebSocket action
EventChannel chan *WebSocketEvent
ResponseChannel chan *WebSocketResponse
+ ListenError *AppError
}
// NewWebSocketClient constructs a new WebSocket client with convienence
@@ -37,6 +38,7 @@ func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) {
1,
make(chan *WebSocketEvent, 100),
make(chan *WebSocketResponse, 100),
+ nil,
}, nil
}
@@ -59,10 +61,20 @@ func (wsc *WebSocketClient) Close() {
func (wsc *WebSocketClient) Listen() {
go func() {
+ defer func() {
+ wsc.Conn.Close()
+ close(wsc.EventChannel)
+ close(wsc.ResponseChannel)
+ }()
+
for {
var rawMsg json.RawMessage
var err error
if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil {
+ if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
+ wsc.ListenError = NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
+ }
+
return
}
@@ -107,3 +119,12 @@ func (wsc *WebSocketClient) UserTyping(channelId, parentId string) {
func (wsc *WebSocketClient) GetStatuses() {
wsc.SendMessage("get_statuses", nil)
}
+
+// GetStatusesByIds will fetch certain user statuses based on ids and return
+// a map of string statuses using user id as the key
+func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) {
+ data := map[string]interface{}{
+ "user_ids": userIds,
+ }
+ wsc.SendMessage("get_statuses_by_ids", data)
+}
diff --git a/model/websocket_message.go b/model/websocket_message.go
index 9bce1e825..c7ab04857 100644
--- a/model/websocket_message.go
+++ b/model/websocket_message.go
@@ -31,6 +31,8 @@ const (
type WebSocketMessage interface {
ToJson() string
IsValid() bool
+ DoPreComputeJson()
+ GetPreComputeJson() []byte
}
type WebsocketBroadcast struct {
@@ -41,9 +43,10 @@ type WebsocketBroadcast struct {
}
type WebSocketEvent struct {
- Event string `json:"event"`
- Data map[string]interface{} `json:"data"`
- Broadcast *WebsocketBroadcast `json:"broadcast"`
+ Event string `json:"event"`
+ Data map[string]interface{} `json:"data"`
+ Broadcast *WebsocketBroadcast `json:"broadcast"`
+ PreComputeJson []byte `json:"-"`
}
func (m *WebSocketEvent) Add(key string, value interface{}) {
@@ -59,6 +62,19 @@ func (o *WebSocketEvent) IsValid() bool {
return o.Event != ""
}
+func (o *WebSocketEvent) DoPreComputeJson() {
+ b, err := json.Marshal(o)
+ if err != nil {
+ o.PreComputeJson = []byte("")
+ } else {
+ o.PreComputeJson = b
+ }
+}
+
+func (o *WebSocketEvent) GetPreComputeJson() []byte {
+ return o.PreComputeJson
+}
+
func (o *WebSocketEvent) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
@@ -80,10 +96,11 @@ func WebSocketEventFromJson(data io.Reader) *WebSocketEvent {
}
type WebSocketResponse struct {
- Status string `json:"status"`
- SeqReply int64 `json:"seq_reply,omitempty"`
- Data map[string]interface{} `json:"data,omitempty"`
- Error *AppError `json:"error,omitempty"`
+ Status string `json:"status"`
+ SeqReply int64 `json:"seq_reply,omitempty"`
+ Data map[string]interface{} `json:"data,omitempty"`
+ Error *AppError `json:"error,omitempty"`
+ PreComputeJson []byte `json:"-"`
}
func (m *WebSocketResponse) Add(key string, value interface{}) {
@@ -111,6 +128,19 @@ func (o *WebSocketResponse) ToJson() string {
}
}
+func (o *WebSocketResponse) DoPreComputeJson() {
+ b, err := json.Marshal(o)
+ if err != nil {
+ o.PreComputeJson = []byte("")
+ } else {
+ o.PreComputeJson = b
+ }
+}
+
+func (o *WebSocketResponse) GetPreComputeJson() []byte {
+ return o.PreComputeJson
+}
+
func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse {
decoder := json.NewDecoder(data)
var o WebSocketResponse