Merging performance branch into master (#4268)
* improve performance on sendNotifications * Fix SQL queries * Remove get direct profiles, not needed anymore * Add raw data to error details if AppError fails to decode * men * Fix decode (#4052) * Fixing json decode * Adding unit test * Initial work for client scaling (#4051) * Begin adding paging to profiles API * Added more paging functionality * Finish hooking up admin console user lists * Add API for searching users and add searching to all user lists * Add lazy loading of profiles * Revert config.json * Fix unit tests and some style issues * Add GetProfilesFromList to Go driver and fix web unit test * Update etag for GetProfiles * Updating ui for filters and pagination (#4044) * Updating UI for pagination * Adjusting margins for filter row * Adjusting margin for specific modals * Adding relative padding to system console * Adjusting responsive view * Update client user tests * Minor fixes for direct messages modal (#4056) * Remove some unneeded initial load calls (#4057) * UX updates to user lists, added smart counts and bug fixes (#4059) * Improved getExplicitMentions and unit tests (#4064) * Refactor getting posts to lazy load profiles correctly (#4062) * Comment out SetActiveChannel test (#4066) * Profiler cpu, block, and memory profiler. (#4081) * Fix TestSetActiveChannel unit test (#4071) * Fixing build failure caused by dependancies updating (#4076) * Adding profiler * Fix admin_team_member_dropdown eslint errors * Bumping session cache size (#4077) * Bumping session cache size * Bumping status cache * Refactor how the client handles channel members to be large team friendly (#4106) * Refactor how the client handles channel members to be large team friendly * Change Id to ChannelId in ChannelStats model * Updated getChannelMember and getProfilesByIds routes to match proposal * Performance improvements (#4100) * Performance improvements * Fixing re-connect issue * Fixing error message * Some other minor perf tweaks * Some other minor perf tweaks * Fixing config file * Fixing buffer size * Fixing web socket send message * adding some error logging * fix getMe to be user required * Fix websocket event for new user * Fixing shutting down * Reverting web socket changes * Fixing logging lvl * Adding caching to GetMember * Adding some logging * Fixing caching * Fixing caching invalidate * Fixing direct message caching * Fixing caching * Fixing caching * Remove GetDirectProfiles from initial load * Adding logging and fixing websocket client * Adding back caching from bad merge. * Explicitly close go driver requests (#4162) * Refactored how the client handles team members to be more large team friendly (#4159) * Refactor getProfilesForDirectMessageList API into getAllProfiles API * Refactored how the client handles team members to be more large team friendly * Fix js error when receiving a notification * Fix JS error caused by current user being overwritten with sanitized version (#4165) * Adding error message to status failure (#4167) * Fix a few bugs caused by client scaling refactoring (#4170) * When there is no read replica, don't open a second set of connections to the master database (#4173) * Adding connection tacking to stats (#4174) * Reduce DB writes for statuses and other status related changes (#4175) * Fix bug preventing opening of DM channels from more modal (#4181) * Fixing socket timing error (#4183) * Fixing ping/pong handler * Fixing socket timing error * Commenting out status broadcasting * Removing user status changes * Removing user status changes * Removing user status changes * Removing user status changes * Adding DoPreComputeJson() * Performance improvements (#4194) * * Fix System Console Analytics queries * Add db.SetConnMaxLifetime to 15 minutes * Add "net/http/pprof" for profiling * Add FreeOSMemory() to manually release memory on reload config * Add flag to enable http profiler * Fix memory leak (#4197) * Fix memory leak * removed unneeded nil assignment * Fixing go routine leak (#4208) * Merge fixes * Merge fix * Refactored statuses to be queried by the client rather than broadcast by the server (#4212) * Refactored server code to reduce status broadcasts and to allow getting statuses by IDs * Refactor client code to periodically fetch statuses * Add store unit test for getting statuses by ids * Fix status unit test * Add getStatusesByIds REST API and move the client over to use that instead of the WebSocket * Adding multiple threads to websocket hub (#4230) * Adding multiple threads to websocket hub * Fixing unit tests * Fixing so websocket connections from the same user end up in the same… (#4240) * Fixing so websocket connections from the same user end up in the same list * Removing old comment * Refactor user autocomplete to query the server (#4239) * Add API for autocompleting users * Converted at mention autocomplete to query server * Converted user search autocomplete to query server * Switch autocomplete API naming to use term instead of username * Split autocomplete API into two, one for channels and for teams * Fix copy/paste error * Some final client scaling fixes (#4246) * Add lazy loading of profiles to integration pages * Add lazy loading of profiles to emoji page * Fix JS error when receiving post in select team menu and also clean up channel store
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0512bd26ee
Коммит
365b8b465e
37
api/admin.go
37
api/admin.go
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"github.com/mssola/user_agent"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func InitAdmin() {
|
||||
@@ -48,7 +49,7 @@ func InitAdmin() {
|
||||
BaseRoutes.Admin.Handle("/remove_certificate", ApiAdminSystemRequired(removeCertificate)).Methods("POST")
|
||||
BaseRoutes.Admin.Handle("/saml_cert_status", ApiAdminSystemRequired(samlCertificateStatus)).Methods("GET")
|
||||
BaseRoutes.Admin.Handle("/cluster_status", ApiAdminSystemRequired(getClusterStatus)).Methods("GET")
|
||||
BaseRoutes.Admin.Handle("/recently_active_users/{team_id:[A-Za-z0-9]+}", ApiUserRequiredActivity(getRecentlyActiveUsers, false)).Methods("GET")
|
||||
BaseRoutes.Admin.Handle("/recently_active_users/{team_id:[A-Za-z0-9]+}", ApiUserRequired(getRecentlyActiveUsers)).Methods("GET")
|
||||
}
|
||||
|
||||
func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -134,6 +135,7 @@ func getConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func reloadConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
debug.FreeOSMemory()
|
||||
utils.LoadConfig(utils.CfgFileName)
|
||||
|
||||
// start/restart email batching job if necessary
|
||||
@@ -338,12 +340,15 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
name := params["name"]
|
||||
|
||||
if name == "standard" {
|
||||
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 5)
|
||||
var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 8)
|
||||
rows[0] = &model.AnalyticsRow{"channel_open_count", 0}
|
||||
rows[1] = &model.AnalyticsRow{"channel_private_count", 0}
|
||||
rows[2] = &model.AnalyticsRow{"post_count", 0}
|
||||
rows[3] = &model.AnalyticsRow{"unique_user_count", 0}
|
||||
rows[4] = &model.AnalyticsRow{"team_count", 0}
|
||||
rows[5] = &model.AnalyticsRow{"total_websocket_connections", 0}
|
||||
rows[6] = &model.AnalyticsRow{"total_master_db_connections", 0}
|
||||
rows[7] = &model.AnalyticsRow{"total_read_db_connections", 0}
|
||||
|
||||
openChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
|
||||
privateChan := Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
|
||||
@@ -386,6 +391,10 @@ func getAnalytics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
rows[4].Value = float64(r.Data.(int64))
|
||||
}
|
||||
|
||||
rows[5].Value = float64(TotalWebsocketConnections())
|
||||
rows[6].Value = float64(Srv.Store.TotalMasterDbConnections())
|
||||
rows[7].Value = float64(Srv.Store.TotalReadDbConnections())
|
||||
|
||||
w.Write([]byte(rows.ToJson()))
|
||||
} else if name == "post_counts_day" {
|
||||
if r := <-Srv.Store.Post().AnalyticsPostCountsByDay(teamId); r.Err != nil {
|
||||
@@ -706,32 +715,14 @@ func samlCertificateStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func getRecentlyActiveUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
statusMap := map[string]interface{}{}
|
||||
|
||||
if result := <-Srv.Store.Status().GetAllFromTeam(c.TeamId); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
statuses := result.Data.([]*model.Status)
|
||||
for _, s := range statuses {
|
||||
statusMap[s.UserId] = s.LastActivityAt
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.User().GetProfiles(c.TeamId); result.Err != nil {
|
||||
if result := <-Srv.Store.User().GetRecentlyActiveUsersForTeam(c.TeamId); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
profiles := result.Data.(map[string]*model.User)
|
||||
|
||||
for k, p := range profiles {
|
||||
p = sanitizeProfile(c, p)
|
||||
|
||||
if lastActivityAt, ok := statusMap[p.Id].(int64); ok {
|
||||
p.LastActivityAt = lastActivityAt
|
||||
}
|
||||
|
||||
profiles[k] = p
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||
|
||||
@@ -527,17 +527,13 @@ func TestAdminLdapSyncNow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Needs more work
|
||||
func TestGetRecentlyActiveUsers(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
user1Id := th.BasicUser.Id
|
||||
user2Id := th.BasicUser2.Id
|
||||
|
||||
if userMap, err := th.BasicClient.GetRecentlyActiveUsers(th.BasicTeam.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(userMap.Data.(map[string]*model.User)) != 2 {
|
||||
t.Fatal("should have been 2")
|
||||
} else if userMap.Data.(map[string]*model.User)[user1Id].Id != user1Id || userMap.Data.(map[string]*model.User)[user2Id].Id != user2Id {
|
||||
t.Fatal("should have been valid")
|
||||
} else if len(userMap.Data.(map[string]*model.User)) >= 2 {
|
||||
t.Fatal("should have been at least 2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func SetupEnterprise() *TestHelper {
|
||||
*utils.Cfg.RateLimitSettings.Enable = false
|
||||
utils.DisableDebugLogForTest()
|
||||
utils.License.Features.SetDefaults()
|
||||
NewServer()
|
||||
NewServer(false)
|
||||
StartServer()
|
||||
utils.InitHTML()
|
||||
InitApi()
|
||||
@@ -57,7 +57,7 @@ func Setup() *TestHelper {
|
||||
utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
|
||||
*utils.Cfg.RateLimitSettings.Enable = false
|
||||
utils.DisableDebugLogForTest()
|
||||
NewServer()
|
||||
NewServer(false)
|
||||
StartServer()
|
||||
InitApi()
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
@@ -5,6 +5,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/model"
|
||||
@@ -65,15 +66,16 @@ func HasPermissionToTeam(user *model.User, teamMember *model.TeamMember, permiss
|
||||
}
|
||||
|
||||
func HasPermissionToChannelContext(c *Context, channelId string, permission *model.Permission) bool {
|
||||
cmc := Srv.Store.Channel().GetMember(channelId, c.Session.UserId)
|
||||
cmc := Srv.Store.Channel().GetAllChannelMembersForUser(c.Session.UserId, true)
|
||||
|
||||
var channelRoles []string
|
||||
if cmcresult := <-cmc; cmcresult.Err == nil {
|
||||
channelMember := cmcresult.Data.(model.ChannelMember)
|
||||
channelRoles = channelMember.GetRoles()
|
||||
|
||||
if CheckIfRolesGrantPermission(channelRoles, permission.Id) {
|
||||
return true
|
||||
ids := cmcresult.Data.(map[string]string)
|
||||
if roles, ok := ids[channelId]; ok {
|
||||
channelRoles = strings.Fields(roles)
|
||||
if CheckIfRolesGrantPermission(channelRoles, permission.Id) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,13 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) {
|
||||
|
||||
ruser := result.Data.(*model.User)
|
||||
|
||||
status := &model.Status{ruser.Id, model.STATUS_ONLINE, false, model.GetMillis(), ""}
|
||||
if result := <-Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
|
||||
result.Err.Translate(utils.T)
|
||||
l4g.Error(result.Err.Error())
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// We need to cheat to verify the user's email
|
||||
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
@@ -16,16 +15,12 @@ import (
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultExtraMemberLimit = 100
|
||||
)
|
||||
|
||||
func InitChannel() {
|
||||
l4g.Debug(utils.T("api.channel.init.debug"))
|
||||
|
||||
BaseRoutes.Channels.Handle("/", ApiUserRequiredActivity(getChannels, false)).Methods("GET")
|
||||
BaseRoutes.Channels.Handle("/", ApiUserRequired(getChannels)).Methods("GET")
|
||||
BaseRoutes.Channels.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET")
|
||||
BaseRoutes.Channels.Handle("/counts", ApiUserRequiredActivity(getChannelCounts, false)).Methods("GET")
|
||||
BaseRoutes.Channels.Handle("/counts", ApiUserRequired(getChannelCounts)).Methods("GET")
|
||||
BaseRoutes.Channels.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
|
||||
BaseRoutes.Channels.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
|
||||
BaseRoutes.Channels.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
|
||||
@@ -35,9 +30,9 @@ func InitChannel() {
|
||||
|
||||
BaseRoutes.NeedChannelName.Handle("/join", ApiUserRequired(join)).Methods("POST")
|
||||
|
||||
BaseRoutes.NeedChannel.Handle("/", ApiUserRequiredActivity(getChannel, false)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/extra_info/{member_limit:-?[0-9]+}", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/", ApiUserRequired(getChannel)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/stats", ApiUserRequired(getChannelStats)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/members/{user_id:[A-Za-z0-9]+}", ApiUserRequired(getChannelMember)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/join", ApiUserRequired(join)).Methods("POST")
|
||||
BaseRoutes.NeedChannel.Handle("/leave", ApiUserRequired(leave)).Methods("POST")
|
||||
BaseRoutes.NeedChannel.Handle("/delete", ApiUserRequired(deleteChannel)).Methods("POST")
|
||||
@@ -150,11 +145,14 @@ func CreateDirectChannel(userId string, otherUserId string) (*model.Channel, *mo
|
||||
} else {
|
||||
channel := result.Data.(*model.Channel)
|
||||
|
||||
InvalidateCacheForUser(userId)
|
||||
InvalidateCacheForUser(otherUserId)
|
||||
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_DIRECT_ADDED, "", channel.Id, "", nil)
|
||||
message.Add("teammate_id", otherUserId)
|
||||
go Publish(message)
|
||||
|
||||
return result.Data.(*model.Channel), nil
|
||||
return channel, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +564,7 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM
|
||||
|
||||
go func() {
|
||||
InvalidateCacheForUser(user.Id)
|
||||
Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
|
||||
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_ADDED, "", channel.Id, "", nil)
|
||||
message.Add("user_id", user.Id)
|
||||
@@ -609,6 +608,8 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m
|
||||
if _, err := CreatePost(fakeContext, post, false); err != nil {
|
||||
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
|
||||
}
|
||||
|
||||
Srv.Store.User().InvalidateProfilesInChannelCache(result.Data.(*model.Channel).Id)
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Channel().GetByName(teamId, "off-topic"); result.Err != nil {
|
||||
@@ -631,6 +632,8 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m
|
||||
if _, err := CreatePost(fakeContext, post, false); err != nil {
|
||||
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
|
||||
}
|
||||
|
||||
Srv.Store.User().InvalidateProfilesInChannelCache(result.Data.(*model.Channel).Id)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -778,9 +781,9 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.LogAudit("name=" + channel.Name)
|
||||
|
||||
go func() {
|
||||
InvalidateCacheForChannel(channel.Id)
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, c.TeamId, "", "", nil)
|
||||
message.Add("channel_id", channel.Id)
|
||||
|
||||
go Publish(message)
|
||||
|
||||
post := &model.Post{
|
||||
@@ -917,54 +920,27 @@ func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
id := params["channel_id"]
|
||||
|
||||
var memberLimit int
|
||||
if memberLimitString, ok := params["member_limit"]; !ok {
|
||||
memberLimit = defaultExtraMemberLimit
|
||||
} else if memberLimitInt64, err := strconv.ParseInt(memberLimitString, 10, 0); err != nil {
|
||||
c.Err = model.NewLocAppError("getChannelExtraInfo", "api.channel.get_channel_extra_info.member_limit.app_error", nil, err.Error())
|
||||
return
|
||||
} else {
|
||||
memberLimit = int(memberLimitInt64)
|
||||
}
|
||||
|
||||
sc := Srv.Store.Channel().Get(id)
|
||||
var channel *model.Channel
|
||||
if cresult := <-sc; cresult.Err != nil {
|
||||
c.Err = cresult.Err
|
||||
if result := <-sc; result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
channel = cresult.Data.(*model.Channel)
|
||||
channel = result.Data.(*model.Channel)
|
||||
}
|
||||
|
||||
extraEtag := channel.ExtraEtag(memberLimit)
|
||||
if HandleEtag(extraEtag, w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
scm := Srv.Store.Channel().GetMember(id, c.Session.UserId)
|
||||
ecm := Srv.Store.Channel().GetExtraMembers(id, memberLimit)
|
||||
ccm := Srv.Store.Channel().GetMemberCount(id)
|
||||
|
||||
if cmresult := <-scm; cmresult.Err != nil {
|
||||
c.Err = cmresult.Err
|
||||
return
|
||||
} else if ecmresult := <-ecm; ecmresult.Err != nil {
|
||||
c.Err = ecmresult.Err
|
||||
return
|
||||
} else if ccmresult := <-ccm; ccmresult.Err != nil {
|
||||
c.Err = ccmresult.Err
|
||||
if result := <-Srv.Store.Channel().GetMemberCount(id); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
//member := cmresult.Data.(model.ChannelMember)
|
||||
extraMembers := ecmresult.Data.([]model.ExtraMember)
|
||||
memberCount := ccmresult.Data.(int64)
|
||||
memberCount := result.Data.(int64)
|
||||
|
||||
if channel.DeleteAt > 0 {
|
||||
c.Err = model.NewLocAppError("getChannelExtraInfo", "api.channel.get_channel_extra_info.deleted.app_error", nil, "")
|
||||
c.Err = model.NewLocAppError("getChannelStats", "api.channel.get_channel_extra_info.deleted.app_error", nil, "")
|
||||
c.Err.StatusCode = http.StatusBadRequest
|
||||
return
|
||||
}
|
||||
@@ -973,12 +949,29 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
data := model.ChannelExtra{Id: channel.Id, Members: extraMembers, MemberCount: memberCount}
|
||||
w.Header().Set(model.HEADER_ETAG_SERVER, extraEtag)
|
||||
data := model.ChannelStats{ChannelId: channel.Id, MemberCount: memberCount}
|
||||
w.Write([]byte(data.ToJson()))
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
channelId := params["channel_id"]
|
||||
userId := params["user_id"]
|
||||
|
||||
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Channel().GetMember(channelId, userId); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
member := result.Data.(model.ChannelMember)
|
||||
w.Write([]byte(member.ToJson()))
|
||||
}
|
||||
}
|
||||
|
||||
func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
id := params["channel_id"]
|
||||
@@ -1101,6 +1094,7 @@ func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel
|
||||
}
|
||||
|
||||
InvalidateCacheForUser(userIdToRemove)
|
||||
Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
|
||||
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil)
|
||||
message.Add("user_id", userIdToRemove)
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
@@ -1106,7 +1105,7 @@ func TestDeleteChannel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannelExtraInfo(t *testing.T) {
|
||||
func TestGetChannelStats(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
team := th.BasicTeam
|
||||
@@ -1114,115 +1113,13 @@ func TestGetChannelExtraInfo(t *testing.T) {
|
||||
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
rget := Client.Must(Client.GetChannelExtraInfo(channel1.Id, -1, ""))
|
||||
data := rget.Data.(*model.ChannelExtra)
|
||||
if data.Id != channel1.Id {
|
||||
rget := Client.Must(Client.GetChannelStats(channel1.Id, ""))
|
||||
data := rget.Data.(*model.ChannelStats)
|
||||
if data.ChannelId != channel1.Id {
|
||||
t.Fatal("couldnt't get extra info")
|
||||
} else if len(data.Members) != 1 {
|
||||
t.Fatal("got incorrect members")
|
||||
} else if data.MemberCount != 1 {
|
||||
t.Fatal("got incorrect member count")
|
||||
}
|
||||
|
||||
//
|
||||
// Testing etag caching
|
||||
//
|
||||
|
||||
currentEtag := rget.Etag
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
||||
t.Log(cache_result.Data)
|
||||
t.Fatal("response should be empty")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
Client2 := model.NewClient("http://localhost" + utils.Cfg.ServiceSettings.ListenAddress)
|
||||
|
||||
user2 := &model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Tester 2", Password: "passwd1"}
|
||||
user2 = Client2.Must(Client2.CreateUser(user2, "")).Data.(*model.User)
|
||||
LinkUserToTeam(user2, team)
|
||||
Client2.SetTeamId(team.Id)
|
||||
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
|
||||
|
||||
Client2.Login(user2.Email, "passwd1")
|
||||
Client2.Must(Client2.JoinChannel(channel1.Id))
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(*model.ChannelExtra) == nil {
|
||||
t.Log(cache_result.Data)
|
||||
t.Fatal("response should not be empty")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
||||
t.Log(cache_result.Data)
|
||||
t.Fatal("response should be empty")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
Client2.Must(Client2.LeaveChannel(channel1.Id))
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(*model.ChannelExtra) == nil {
|
||||
t.Log(cache_result.Data)
|
||||
t.Fatal("response should not be empty")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
||||
t.Log(cache_result.Data)
|
||||
t.Fatal("response should be empty")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
Client2.Must(Client2.JoinChannel(channel1.Id))
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 2, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil {
|
||||
t.Fatal("response should not be empty")
|
||||
} else if len(extra.Members) != 2 {
|
||||
t.Fatal("should've returned 2 members")
|
||||
} else if extra.MemberCount != 2 {
|
||||
t.Fatal("should've returned member count of 2")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if extra := cache_result.Data.(*model.ChannelExtra); extra == nil {
|
||||
t.Fatal("response should not be empty")
|
||||
} else if len(extra.Members) != 1 {
|
||||
t.Fatal("should've returned only 1 member")
|
||||
} else if extra.MemberCount != 2 {
|
||||
t.Fatal("should've returned member count of 2")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, 1, currentEtag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(*model.ChannelExtra) != nil {
|
||||
t.Log(cache_result.Data)
|
||||
t.Fatal("response should be empty")
|
||||
} else {
|
||||
currentEtag = cache_result.Etag
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAddChannelMember(t *testing.T) {
|
||||
@@ -1495,3 +1392,41 @@ func TestFuzzyChannel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannelMember(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
team := th.BasicTeam
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||
|
||||
if result, err := Client.GetChannelMember(channel1.Id, th.BasicUser.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
cm := result.Data.(*model.ChannelMember)
|
||||
|
||||
if cm.UserId != th.BasicUser.Id {
|
||||
t.Fatal("user ids didn't match")
|
||||
}
|
||||
if cm.ChannelId != channel1.Id {
|
||||
t.Fatal("channel ids didn't match")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := Client.GetChannelMember(channel1.Id, th.BasicUser2.Id); err == nil {
|
||||
t.Fatal("should have failed - user not in channel")
|
||||
}
|
||||
|
||||
if _, err := Client.GetChannelMember("junk", th.BasicUser2.Id); err == nil {
|
||||
t.Fatal("should have failed - bad channel id")
|
||||
}
|
||||
|
||||
if _, err := Client.GetChannelMember(channel1.Id, "junk"); err == nil {
|
||||
t.Fatal("should have failed - bad user id")
|
||||
}
|
||||
|
||||
if _, err := Client.GetChannelMember("junk", "junk"); err == nil {
|
||||
t.Fatal("should have failed - bad channel and user id")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestCliCreateUserWithTeam(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfiles(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
|
||||
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
|
||||
|
||||
found := false
|
||||
|
||||
@@ -318,7 +318,7 @@ func TestCliJoinTeam(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfiles(th.SystemAdminTeam.Id, "")).Data.(map[string]*model.User)
|
||||
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
|
||||
|
||||
found := false
|
||||
|
||||
@@ -348,7 +348,7 @@ func TestCliLeaveTeam(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
profiles := th.BasicClient.Must(th.BasicClient.GetProfiles(th.BasicTeam.Id, "")).Data.(map[string]*model.User)
|
||||
profiles := th.BasicClient.Must(th.BasicClient.GetProfilesInTeam(th.BasicTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
|
||||
|
||||
found := false
|
||||
|
||||
@@ -359,8 +359,8 @@ func TestCliLeaveTeam(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("profile still should be in team even if deleted")
|
||||
if found {
|
||||
t.Fatal("profile should not be on team")
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Team().GetTeamsByUserId(th.BasicUser.Id); result.Err != nil {
|
||||
|
||||
@@ -288,7 +288,7 @@ func (me *LoadTestProvider) PostsCommand(c *Context, channelId string, message s
|
||||
}
|
||||
|
||||
var usernames []string
|
||||
if result := <-Srv.Store.User().GetProfiles(c.TeamId); result.Err == nil {
|
||||
if result := <-Srv.Store.User().GetProfiles(c.TeamId, 0, 1000); result.Err == nil {
|
||||
profileUsers := result.Data.(map[string]*model.User)
|
||||
usernames = make([]string, len(profileUsers))
|
||||
i := 0
|
||||
|
||||
@@ -47,20 +47,22 @@ func (me *msgProvider) DoCommand(c *Context, channelId string, message string) *
|
||||
targetUser = strings.SplitN(message, " ", 2)[0]
|
||||
targetUser = strings.TrimPrefix(targetUser, "@")
|
||||
|
||||
if profileList := <-Srv.Store.User().GetAllProfiles(); profileList.Err != nil {
|
||||
// FIX ME
|
||||
// Why isn't this selecting by username since we have that?
|
||||
if profileList := <-Srv.Store.User().GetAll(); profileList.Err != nil {
|
||||
c.Err = profileList.Err
|
||||
return &model.CommandResponse{Text: c.T("api.command_msg.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
|
||||
} else {
|
||||
profileUsers := profileList.Data.(map[string]*model.User)
|
||||
profileUsers := profileList.Data.([]*model.User)
|
||||
for _, userProfile := range profileUsers {
|
||||
//Don't let users open DMs with themselves. It probably won't work out well.
|
||||
// Don't let users open DMs with themselves. It probably won't work out well.
|
||||
if userProfile.Id == c.Session.UserId {
|
||||
continue
|
||||
}
|
||||
if userProfile.Username == targetUser {
|
||||
targetChannelId := ""
|
||||
|
||||
//Find the channel based on this user
|
||||
// Find the channel based on this user
|
||||
channelName := model.GetDMNameFromIds(c.Session.UserId, userProfile.Id)
|
||||
|
||||
if channel := <-Srv.Store.Channel().GetByName(c.TeamId, channelName); channel.Err != nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ func commandAndTest(t *testing.T, th *TestHelper, status string) {
|
||||
t.Fatal("Command failed to execute")
|
||||
}
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
statuses := Client.Must(Client.GetStatuses()).Data.(map[string]string)
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ func AppHandlerIndependent(h func(*Context, http.ResponseWriter, *http.Request))
|
||||
}
|
||||
|
||||
func ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return &handler{h, true, false, true, true, false, false}
|
||||
return &handler{h, true, false, true, false, false, false}
|
||||
}
|
||||
|
||||
func ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler {
|
||||
@@ -85,7 +85,7 @@ func ApiAppHandlerTrustRequester(h func(*Context, http.ResponseWriter, *http.Req
|
||||
}
|
||||
|
||||
func ApiUserRequiredTrustRequester(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return &handler{h, true, false, true, true, false, true}
|
||||
return &handler{h, true, false, true, false, false, true}
|
||||
}
|
||||
|
||||
func ApiAppHandlerTrustRequesterIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
|
||||
@@ -220,7 +220,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c.LogError(c.Err)
|
||||
c.Err.Where = r.URL.Path
|
||||
|
||||
// Block out detailed error whenn not in developer mode
|
||||
// Block out detailed error when not in developer mode
|
||||
if !*utils.Cfg.ServiceSettings.EnableDeveloper {
|
||||
c.Err.DetailedError = ""
|
||||
}
|
||||
|
||||
145
api/post.go
145
api/post.go
@@ -35,18 +35,18 @@ const (
|
||||
func InitPost() {
|
||||
l4g.Debug(utils.T("api.post.init.debug"))
|
||||
|
||||
BaseRoutes.NeedTeam.Handle("/posts/search", ApiUserRequired(searchPosts)).Methods("POST")
|
||||
BaseRoutes.NeedTeam.Handle("/posts/flagged/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getFlaggedPosts, false)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/posts/search", ApiUserRequiredActivity(searchPosts, true)).Methods("POST")
|
||||
BaseRoutes.NeedTeam.Handle("/posts/flagged/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getFlaggedPosts)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/posts/{post_id}", ApiUserRequired(getPostById)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/pltmp/{post_id}", ApiUserRequired(getPermalinkTmp)).Methods("GET")
|
||||
|
||||
BaseRoutes.Posts.Handle("/create", ApiUserRequired(createPost)).Methods("POST")
|
||||
BaseRoutes.Posts.Handle("/update", ApiUserRequired(updatePost)).Methods("POST")
|
||||
BaseRoutes.Posts.Handle("/page/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getPosts, false)).Methods("GET")
|
||||
BaseRoutes.Posts.Handle("/since/{time:[0-9]+}", ApiUserRequiredActivity(getPostsSince, false)).Methods("GET")
|
||||
BaseRoutes.Posts.Handle("/create", ApiUserRequiredActivity(createPost, true)).Methods("POST")
|
||||
BaseRoutes.Posts.Handle("/update", ApiUserRequiredActivity(updatePost, true)).Methods("POST")
|
||||
BaseRoutes.Posts.Handle("/page/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getPosts)).Methods("GET")
|
||||
BaseRoutes.Posts.Handle("/since/{time:[0-9]+}", ApiUserRequired(getPostsSince)).Methods("GET")
|
||||
|
||||
BaseRoutes.NeedPost.Handle("/get", ApiUserRequired(getPost)).Methods("GET")
|
||||
BaseRoutes.NeedPost.Handle("/delete", ApiUserRequired(deletePost)).Methods("POST")
|
||||
BaseRoutes.NeedPost.Handle("/delete", ApiUserRequiredActivity(deletePost, true)).Methods("POST")
|
||||
BaseRoutes.NeedPost.Handle("/before/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsBefore)).Methods("GET")
|
||||
BaseRoutes.NeedPost.Handle("/after/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsAfter)).Methods("GET")
|
||||
BaseRoutes.NeedPost.Handle("/get_file_infos", ApiUserRequired(getFileInfosForPost)).Methods("GET")
|
||||
@@ -154,7 +154,7 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
|
||||
}
|
||||
}
|
||||
|
||||
go handlePostEvents(c, rpost, triggerWebhooks)
|
||||
handlePostEvents(c, rpost, triggerWebhooks)
|
||||
|
||||
return rpost, nil
|
||||
}
|
||||
@@ -250,7 +250,7 @@ func handlePostEvents(c *Context, post *model.Post, triggerWebhooks bool) {
|
||||
channel = result.Data.(*model.Channel)
|
||||
}
|
||||
|
||||
go sendNotifications(c, post, team, channel)
|
||||
sendNotifications(c, post, team, channel)
|
||||
|
||||
var user *model.User
|
||||
if result := <-uchan; result.Err != nil {
|
||||
@@ -441,40 +441,31 @@ func handleWebhookEvents(c *Context, post *model.Post, team *model.Team, channel
|
||||
}
|
||||
}
|
||||
|
||||
// Given a map of user IDs to profiles and a map of user IDs of channel members, returns a list of mention
|
||||
// keywords for all users on the team. Users that are members of the channel will have all their mention
|
||||
// keywords returned while users that aren't in the channel will only have their @mentions returned.
|
||||
func getMentionKeywords(profiles map[string]*model.User, members map[string]string) map[string][]string {
|
||||
// Given a map of user IDs to profiles, returns a list of mention
|
||||
// keywords for all users in the channel.
|
||||
func getMentionKeywordsInChannel(profiles map[string]*model.User) map[string][]string {
|
||||
keywords := make(map[string][]string)
|
||||
|
||||
for id, profile := range profiles {
|
||||
_, inChannel := members[id]
|
||||
|
||||
if inChannel {
|
||||
if len(profile.NotifyProps["mention_keys"]) > 0 {
|
||||
// Add all the user's mention keys
|
||||
splitKeys := strings.Split(profile.NotifyProps["mention_keys"], ",")
|
||||
for _, k := range splitKeys {
|
||||
// note that these are made lower case so that we can do a case insensitive check for them
|
||||
key := strings.ToLower(k)
|
||||
keywords[key] = append(keywords[key], id)
|
||||
}
|
||||
if len(profile.NotifyProps["mention_keys"]) > 0 {
|
||||
// Add all the user's mention keys
|
||||
splitKeys := strings.Split(profile.NotifyProps["mention_keys"], ",")
|
||||
for _, k := range splitKeys {
|
||||
// note that these are made lower case so that we can do a case insensitive check for them
|
||||
key := strings.ToLower(k)
|
||||
keywords[key] = append(keywords[key], id)
|
||||
}
|
||||
}
|
||||
|
||||
// If turned on, add the user's case sensitive first name
|
||||
if profile.NotifyProps["first_name"] == "true" {
|
||||
keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
|
||||
}
|
||||
// If turned on, add the user's case sensitive first name
|
||||
if profile.NotifyProps["first_name"] == "true" {
|
||||
keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
|
||||
}
|
||||
|
||||
// Add @channel and @all to keywords if user has them turned on
|
||||
if profile.NotifyProps["channel"] == "true" {
|
||||
keywords["@channel"] = append(keywords["@channel"], profile.Id)
|
||||
keywords["@all"] = append(keywords["@all"], profile.Id)
|
||||
}
|
||||
} else {
|
||||
// user isn't in channel, so just look for @mentions
|
||||
key := "@" + strings.ToLower(profile.Username)
|
||||
keywords[key] = append(keywords[key], id)
|
||||
// Add @channel and @all to keywords if user has them turned on
|
||||
if profile.NotifyProps["channel"] == "true" {
|
||||
keywords["@channel"] = append(keywords["@channel"], profile.Id)
|
||||
keywords["@all"] = append(keywords["@all"], profile.Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,9 +473,11 @@ func getMentionKeywords(profiles map[string]*model.User, members map[string]stri
|
||||
}
|
||||
|
||||
// Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned
|
||||
// users and whether or not @here was mentioned.
|
||||
func getExplicitMentions(message string, keywords map[string][]string) (map[string]bool, bool) {
|
||||
// users and a slice of potencial mention users not in the channel and whether or not @here was mentioned.
|
||||
func getExplicitMentions(message string, keywords map[string][]string) (map[string]bool, []string, bool) {
|
||||
mentioned := make(map[string]bool)
|
||||
potentialOthersMentioned := make([]string, 0)
|
||||
systemMentions := map[string]bool{"@here": true, "@channel": true, "@all": true}
|
||||
hereMentioned := false
|
||||
|
||||
addMentionedUsers := func(ids []string) {
|
||||
@@ -510,6 +503,9 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri
|
||||
if ids, match := keywords[word]; match {
|
||||
addMentionedUsers(ids)
|
||||
isMention = true
|
||||
} else if _, ok := systemMentions[word]; !ok && strings.HasPrefix(word, "@") {
|
||||
potentialOthersMentioned = append(potentialOthersMentioned, word[1:])
|
||||
continue
|
||||
}
|
||||
|
||||
if !isMention {
|
||||
@@ -532,19 +528,19 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri
|
||||
// Case-sensitive check for first name
|
||||
if ids, match := keywords[splitWord]; match {
|
||||
addMentionedUsers(ids)
|
||||
} else if _, ok := systemMentions[word]; !ok && strings.HasPrefix(word, "@") {
|
||||
username := word[1:len(splitWord)]
|
||||
potentialOthersMentioned = append(potentialOthersMentioned, username)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mentioned, hereMentioned
|
||||
return mentioned, potentialOthersMentioned, hereMentioned
|
||||
}
|
||||
|
||||
func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel) {
|
||||
// get profiles for all users we could be mentioning
|
||||
pchan := Srv.Store.User().GetProfiles(c.TeamId)
|
||||
dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
|
||||
mchan := Srv.Store.Channel().GetMembers(post.ChannelId)
|
||||
pchan := Srv.Store.User().GetProfilesInChannel(channel.Id, -1, -1, true)
|
||||
fchan := Srv.Store.FileInfo().GetForPost(post.Id)
|
||||
|
||||
var profileMap map[string]*model.User
|
||||
@@ -555,30 +551,11 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
||||
profileMap = result.Data.(map[string]*model.User)
|
||||
}
|
||||
|
||||
if result := <-dpchan; result.Err != nil {
|
||||
l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err)
|
||||
return
|
||||
} else {
|
||||
dps := result.Data.(map[string]*model.User)
|
||||
for k, v := range dps {
|
||||
profileMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// If the user who made the post is mention don't send a notification
|
||||
if _, ok := profileMap[post.UserId]; !ok {
|
||||
l4g.Error(utils.T("api.post.send_notifications_and_forget.user_id.error"), post.UserId)
|
||||
return
|
||||
}
|
||||
// using a map as a pseudo-set since we're checking for containment a lot
|
||||
members := make(map[string]string)
|
||||
if result := <-mchan; result.Err != nil {
|
||||
l4g.Error(utils.T("api.post.handle_post_events_and_forget.members.error"), post.ChannelId, result.Err)
|
||||
return
|
||||
} else {
|
||||
for _, member := range result.Data.([]model.ChannelMember) {
|
||||
members[member.UserId] = member.UserId
|
||||
}
|
||||
}
|
||||
|
||||
mentionedUserIds := make(map[string]bool)
|
||||
allActivityPushUserIds := []string{}
|
||||
@@ -595,11 +572,11 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
||||
|
||||
mentionedUserIds[otherUserId] = true
|
||||
} else {
|
||||
keywords := getMentionKeywords(profileMap, members)
|
||||
keywords := getMentionKeywordsInChannel(profileMap)
|
||||
|
||||
// get users that are explicitly mentioned
|
||||
var mentioned map[string]bool
|
||||
mentioned, hereNotification = getExplicitMentions(post.Message, keywords)
|
||||
var potentialOtherMentions []string
|
||||
mentioned, potentialOtherMentions, hereNotification = getExplicitMentions(post.Message, keywords)
|
||||
|
||||
// get users that have comment thread mentions enabled
|
||||
if len(post.RootId) > 0 {
|
||||
@@ -623,25 +600,15 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
||||
delete(mentioned, post.UserId)
|
||||
}
|
||||
|
||||
outOfChannelMentions := make(map[string]bool)
|
||||
for id := range mentioned {
|
||||
if _, inChannel := members[id]; inChannel {
|
||||
mentionedUserIds[id] = true
|
||||
} else {
|
||||
outOfChannelMentions[id] = true
|
||||
if len(potentialOtherMentions) > 0 {
|
||||
if result := <-Srv.Store.User().GetProfilesByUsernames(potentialOtherMentions, team.Id); result.Err == nil {
|
||||
outOfChannelMentions := result.Data.(map[string]*model.User)
|
||||
go sendOutOfChannelMentions(c, post, outOfChannelMentions)
|
||||
}
|
||||
}
|
||||
|
||||
go sendOutOfChannelMentions(c, post, profileMap, outOfChannelMentions)
|
||||
|
||||
// find which users in the channel are set up to always receive mobile notifications
|
||||
for id := range members {
|
||||
profile := profileMap[id]
|
||||
if profile == nil {
|
||||
l4g.Warn(utils.T("api.post.notification.member_profile.warn"), id)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, profile := range profileMap {
|
||||
if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL &&
|
||||
(post.UserId != profile.Id || post.Props["from_webhook"] == "true") &&
|
||||
!post.IsSystemMessage() {
|
||||
@@ -699,10 +666,9 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
||||
}
|
||||
|
||||
_, profileFound := profileMap[status.UserId]
|
||||
_, isChannelMember := members[status.UserId]
|
||||
_, alreadyMentioned := mentionedUserIds[status.UserId]
|
||||
|
||||
if status.Status == model.STATUS_ONLINE && profileFound && isChannelMember && !alreadyMentioned {
|
||||
if status.Status == model.STATUS_ONLINE && profileFound && !alreadyMentioned {
|
||||
mentionedUsersList = append(mentionedUsersList, status.UserId)
|
||||
updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, status.UserId))
|
||||
}
|
||||
@@ -787,7 +753,8 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
|
||||
message.Add("mentions", model.ArrayToJson(mentionedUsersList))
|
||||
}
|
||||
|
||||
go Publish(message)
|
||||
Publish(message)
|
||||
return
|
||||
}
|
||||
|
||||
func sendNotificationEmail(c *Context, post *model.Post, user *model.User, channel *model.Channel, team *model.Team, senderName string, sender *model.User) {
|
||||
@@ -1045,14 +1012,14 @@ func getMobileAppSession(userId string) *model.Session {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendOutOfChannelMentions(c *Context, post *model.Post, profiles map[string]*model.User, outOfChannelMentions map[string]bool) {
|
||||
if len(outOfChannelMentions) == 0 {
|
||||
func sendOutOfChannelMentions(c *Context, post *model.Post, profiles map[string]*model.User) {
|
||||
if len(profiles) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var usernames []string
|
||||
for id := range outOfChannelMentions {
|
||||
usernames = append(usernames, profiles[id].Username)
|
||||
for _, user := range profiles {
|
||||
usernames = append(usernames, user.Username)
|
||||
}
|
||||
sort.Strings(usernames)
|
||||
|
||||
|
||||
@@ -883,10 +883,9 @@ func TestGetMentionKeywords(t *testing.T) {
|
||||
}
|
||||
|
||||
profiles := map[string]*model.User{user1.Id: user1}
|
||||
members := map[string]string{user1.Id: user1.Id}
|
||||
mentions := getMentionKeywords(profiles, members)
|
||||
mentions := getMentionKeywordsInChannel(profiles)
|
||||
if len(mentions) != 3 {
|
||||
t.Fatal("should've returned two mention keywords")
|
||||
t.Fatal("should've returned three mention keywords")
|
||||
} else if ids, ok := mentions["user"]; !ok || ids[0] != user1.Id {
|
||||
t.Fatal("should've returned mention key of user")
|
||||
} else if ids, ok := mentions["@user"]; !ok || ids[0] != user1.Id {
|
||||
@@ -906,8 +905,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
||||
}
|
||||
|
||||
profiles = map[string]*model.User{user2.Id: user2}
|
||||
members = map[string]string{user2.Id: user2.Id}
|
||||
mentions = getMentionKeywords(profiles, members)
|
||||
mentions = getMentionKeywordsInChannel(profiles)
|
||||
if len(mentions) != 1 {
|
||||
t.Fatal("should've returned one mention keyword")
|
||||
} else if ids, ok := mentions["First"]; !ok || ids[0] != user2.Id {
|
||||
@@ -925,8 +923,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
||||
}
|
||||
|
||||
profiles = map[string]*model.User{user3.Id: user3}
|
||||
members = map[string]string{user3.Id: user3.Id}
|
||||
mentions = getMentionKeywords(profiles, members)
|
||||
mentions = getMentionKeywordsInChannel(profiles)
|
||||
if len(mentions) != 2 {
|
||||
t.Fatal("should've returned two mention keywords")
|
||||
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
|
||||
@@ -948,8 +945,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
||||
}
|
||||
|
||||
profiles = map[string]*model.User{user4.Id: user4}
|
||||
members = map[string]string{user4.Id: user4.Id}
|
||||
mentions = getMentionKeywords(profiles, members)
|
||||
mentions = getMentionKeywordsInChannel(profiles)
|
||||
if len(mentions) != 6 {
|
||||
t.Fatal("should've returned six mention keywords")
|
||||
} else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id {
|
||||
@@ -973,13 +969,7 @@ func TestGetMentionKeywords(t *testing.T) {
|
||||
user3.Id: user3,
|
||||
user4.Id: user4,
|
||||
}
|
||||
members = map[string]string{
|
||||
user1.Id: user1.Id,
|
||||
user2.Id: user2.Id,
|
||||
user3.Id: user3.Id,
|
||||
user4.Id: user4.Id,
|
||||
}
|
||||
mentions = getMentionKeywords(profiles, members)
|
||||
mentions = getMentionKeywordsInChannel(profiles)
|
||||
if len(mentions) != 6 {
|
||||
t.Fatal("should've returned six mention keywords")
|
||||
} else if ids, ok := mentions["user"]; !ok || len(ids) != 2 || (ids[0] != user1.Id && ids[1] != user1.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
||||
@@ -995,16 +985,6 @@ func TestGetMentionKeywords(t *testing.T) {
|
||||
} else if ids, ok := mentions["@all"]; !ok || len(ids) != 2 || (ids[0] != user3.Id && ids[1] != user3.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
||||
t.Fatal("should've mentioned user3 and user4 with @all")
|
||||
}
|
||||
|
||||
// a user that's not in the channel
|
||||
profiles = map[string]*model.User{user4.Id: user4}
|
||||
members = map[string]string{}
|
||||
mentions = getMentionKeywords(profiles, members)
|
||||
if len(mentions) != 1 {
|
||||
t.Fatal("should've returned one mention keyword")
|
||||
} else if ids, ok := mentions["@user"]; !ok || len(ids) != 1 || ids[0] != user4.Id {
|
||||
t.Fatal("should've returned mention key of @user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExplicitMentionsAtHere(t *testing.T) {
|
||||
@@ -1051,7 +1031,7 @@ func TestGetExplicitMentionsAtHere(t *testing.T) {
|
||||
}
|
||||
|
||||
for message, shouldMention := range cases {
|
||||
if _, hereMentioned := getExplicitMentions(message, nil); hereMentioned && !shouldMention {
|
||||
if _, _, hereMentioned := getExplicitMentions(message, nil); hereMentioned && !shouldMention {
|
||||
t.Fatalf("shouldn't have mentioned @here with \"%v\"", message)
|
||||
} else if !hereMentioned && shouldMention {
|
||||
t.Fatalf("should've have mentioned @here with \"%v\"", message)
|
||||
@@ -1060,10 +1040,12 @@ func TestGetExplicitMentionsAtHere(t *testing.T) {
|
||||
|
||||
// mentioning @here and someone
|
||||
id := model.NewId()
|
||||
if mentions, hereMentioned := getExplicitMentions("@here @user", map[string][]string{"@user": {id}}); !hereMentioned {
|
||||
if mentions, potential, hereMentioned := getExplicitMentions("@here @user @potential", map[string][]string{"@user": {id}}); !hereMentioned {
|
||||
t.Fatal("should've mentioned @here with \"@here @user\"")
|
||||
} else if len(mentions) != 1 || !mentions[id] {
|
||||
t.Fatal("should've mentioned @user with \"@here @user\"")
|
||||
} else if len(potential) > 1 {
|
||||
t.Fatal("should've potential mentions for @potential")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1074,69 +1056,76 @@ func TestGetExplicitMentions(t *testing.T) {
|
||||
// not mentioning anybody
|
||||
message := "this is a message"
|
||||
keywords := map[string][]string{}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
|
||||
t.Fatal("shouldn't have mentioned anybody")
|
||||
if mentions, potential, _ := getExplicitMentions(message, keywords); len(mentions) != 0 || len(potential) != 0 {
|
||||
t.Fatal("shouldn't have mentioned anybody or have any potencial mentions")
|
||||
}
|
||||
|
||||
// mentioning a user that doesn't exist
|
||||
message = "this is a message for @user"
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
|
||||
t.Fatal("shouldn't have mentioned user that doesn't exist")
|
||||
}
|
||||
|
||||
// mentioning one person
|
||||
keywords = map[string][]string{"@user": {id1}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
||||
t.Fatal("should've mentioned @user")
|
||||
}
|
||||
|
||||
// mentioning one person without an @mention
|
||||
message = "this is a message for @user"
|
||||
keywords = map[string][]string{"this": {id1}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
|
||||
t.Fatal("should've mentioned this")
|
||||
}
|
||||
|
||||
// mentioning multiple people with one word
|
||||
message = "this is a message for @user"
|
||||
keywords = map[string][]string{"@user": {id1, id2}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
t.Fatal("should've mentioned two users with @user")
|
||||
}
|
||||
|
||||
// mentioning only one of multiple people
|
||||
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
||||
t.Fatal("should've mentioned @user and not @mention")
|
||||
}
|
||||
|
||||
// mentioning multiple people with multiple words
|
||||
message = "this is an @mention for @user"
|
||||
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
t.Fatal("should've mentioned two users with @user and @mention")
|
||||
}
|
||||
|
||||
// mentioning @channel (not a special case, but it's good to double check)
|
||||
message = "this is an message for @channel"
|
||||
keywords = map[string][]string{"@channel": {id1, id2}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
t.Fatal("should've mentioned two users with @channel")
|
||||
}
|
||||
|
||||
// mentioning @all (not a special case, but it's good to double check)
|
||||
message = "this is an message for @all"
|
||||
keywords = map[string][]string{"@all": {id1, id2}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
|
||||
t.Fatal("should've mentioned two users with @all")
|
||||
}
|
||||
|
||||
// mentioning user.period without mentioning user (PLT-3222)
|
||||
message = "user.period doesn't complicate things at all by including periods in their username"
|
||||
keywords = map[string][]string{"user.period": {id1}, "user": {id2}}
|
||||
if mentions, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
||||
if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
|
||||
t.Fatal("should've mentioned user.period and not user")
|
||||
}
|
||||
|
||||
// mentioning a potential out of channel user
|
||||
message = "this is an message for @potential and @user"
|
||||
keywords = map[string][]string{"@user": {id1}}
|
||||
if mentions, potential, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || len(potential) != 1 {
|
||||
t.Fatal("should've mentioned user and have a potential not in channel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFlaggedPosts(t *testing.T) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -36,7 +37,20 @@ const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
|
||||
|
||||
var Srv *Server
|
||||
|
||||
func NewServer() {
|
||||
func AttachProfiler(router *mux.Router) {
|
||||
router.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
|
||||
// Manually add support for paths linked to by index page at /debug/pprof/
|
||||
router.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
|
||||
router.Handle("/debug/pprof/heap", pprof.Handler("heap"))
|
||||
router.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
|
||||
router.Handle("/debug/pprof/block", pprof.Handler("block"))
|
||||
}
|
||||
|
||||
func NewServer(enableProfiler bool) {
|
||||
|
||||
l4g.Info(utils.T("api.server.new_server.init.info"))
|
||||
|
||||
@@ -44,6 +58,10 @@ func NewServer() {
|
||||
Srv.Store = store.NewSqlStore()
|
||||
|
||||
Srv.Router = mux.NewRouter()
|
||||
if enableProfiler {
|
||||
AttachProfiler(Srv.Router)
|
||||
l4g.Info("Enabled HTTP Profiler")
|
||||
}
|
||||
Srv.Router.NotFoundHandler = http.HandlerFunc(Handle404)
|
||||
}
|
||||
|
||||
@@ -177,7 +195,7 @@ func StopServer() {
|
||||
|
||||
Srv.GracefulServer.Stop(TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
|
||||
Srv.Store.Close()
|
||||
hub.Stop()
|
||||
HubStop()
|
||||
|
||||
l4g.Info(utils.T("api.server.stop_server.stopped.info"))
|
||||
}
|
||||
|
||||
131
api/status.go
131
api/status.go
@@ -31,9 +31,11 @@ func AddStatusCache(status *model.Status) {
|
||||
func InitStatus() {
|
||||
l4g.Debug(utils.T("api.status.init.debug"))
|
||||
|
||||
BaseRoutes.Users.Handle("/status", ApiUserRequiredActivity(getStatusesHttp, false)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/status/set_active_channel", ApiUserRequiredActivity(setActiveChannel, false)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("/status", ApiUserRequired(getStatusesHttp)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/status/ids", ApiUserRequired(getStatusesByIdsHttp)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("/status/set_active_channel", ApiUserRequired(setActiveChannel)).Methods("POST")
|
||||
BaseRoutes.WebSocket.Handle("get_statuses", ApiWebSocketHandler(getStatusesWebSocket))
|
||||
BaseRoutes.WebSocket.Handle("get_statuses_by_ids", ApiWebSocketHandler(getStatusesByIdsWebSocket))
|
||||
}
|
||||
|
||||
func getStatusesHttp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -55,6 +57,7 @@ func getStatusesWebSocket(req *model.WebSocketRequest) (map[string]interface{},
|
||||
return statusMap, nil
|
||||
}
|
||||
|
||||
// Only returns 300 statuses max
|
||||
func GetAllStatuses() (map[string]interface{}, *model.AppError) {
|
||||
if result := <-Srv.Store.Status().GetOnlineAway(); result.Err != nil {
|
||||
return nil, result.Err
|
||||
@@ -70,11 +73,82 @@ func GetAllStatuses() (map[string]interface{}, *model.AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func getStatusesByIdsHttp(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
userIds := model.ArrayFromJson(r.Body)
|
||||
|
||||
if len(userIds) == 0 {
|
||||
c.SetInvalidParam("getStatusesByIdsHttp", "user_ids")
|
||||
return
|
||||
}
|
||||
|
||||
statusMap, err := GetStatusesByIds(userIds)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(model.StringInterfaceToJson(statusMap)))
|
||||
}
|
||||
|
||||
func getStatusesByIdsWebSocket(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) {
|
||||
var userIds []string
|
||||
if userIds = model.ArrayFromInterface(req.Data["user_ids"]); len(userIds) == 0 {
|
||||
l4g.Error(model.StringInterfaceToJson(req.Data))
|
||||
return nil, NewInvalidWebSocketParamError(req.Action, "user_ids")
|
||||
}
|
||||
|
||||
statusMap, err := GetStatusesByIds(userIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return statusMap, nil
|
||||
}
|
||||
|
||||
func GetStatusesByIds(userIds []string) (map[string]interface{}, *model.AppError) {
|
||||
statusMap := map[string]interface{}{}
|
||||
|
||||
missingUserIds := []string{}
|
||||
for _, userId := range userIds {
|
||||
if result, ok := statusCache.Get(userId); ok {
|
||||
statusMap[userId] = result.(*model.Status).Status
|
||||
} else {
|
||||
missingUserIds = append(missingUserIds, userId)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingUserIds) > 0 {
|
||||
if result := <-Srv.Store.Status().GetByIds(missingUserIds); result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
statuses := result.Data.([]*model.Status)
|
||||
|
||||
for _, s := range statuses {
|
||||
AddStatusCache(s)
|
||||
statusMap[s.UserId] = s.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For the case where the user does not have a row in the Status table and cache
|
||||
for _, userId := range missingUserIds {
|
||||
if _, ok := statusMap[userId]; !ok {
|
||||
statusMap[userId] = model.STATUS_OFFLINE
|
||||
}
|
||||
}
|
||||
|
||||
return statusMap, nil
|
||||
}
|
||||
|
||||
func SetStatusOnline(userId string, sessionId string, manual bool) {
|
||||
broadcast := false
|
||||
|
||||
var oldStatus string = model.STATUS_OFFLINE
|
||||
var oldTime int64 = 0
|
||||
var oldManual bool = false
|
||||
var status *model.Status
|
||||
var err *model.AppError
|
||||
|
||||
if status, err = GetStatus(userId); err != nil {
|
||||
status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), ""}
|
||||
broadcast = true
|
||||
@@ -82,35 +156,45 @@ func SetStatusOnline(userId string, sessionId string, manual bool) {
|
||||
if status.Manual && !manual {
|
||||
return // manually set status always overrides non-manual one
|
||||
}
|
||||
|
||||
if status.Status != model.STATUS_ONLINE {
|
||||
broadcast = true
|
||||
}
|
||||
|
||||
oldStatus = status.Status
|
||||
oldTime = status.LastActivityAt
|
||||
oldManual = status.Manual
|
||||
|
||||
status.Status = model.STATUS_ONLINE
|
||||
status.Manual = false // for "online" there's no manually or auto set
|
||||
status.Manual = false // for "online" there's no manual setting
|
||||
status.LastActivityAt = model.GetMillis()
|
||||
}
|
||||
|
||||
AddStatusCache(status)
|
||||
|
||||
achan := Srv.Store.Session().UpdateLastActivityAt(sessionId, model.GetMillis())
|
||||
// Only update the database if the status has changed, the status has been manually set,
|
||||
// or enough time has passed since the previous action
|
||||
if status.Status != oldStatus || status.Manual != oldManual || status.LastActivityAt-oldTime > model.STATUS_MIN_UPDATE_TIME {
|
||||
achan := Srv.Store.Session().UpdateLastActivityAt(sessionId, status.LastActivityAt)
|
||||
|
||||
var schan store.StoreChannel
|
||||
if broadcast {
|
||||
schan = Srv.Store.Status().SaveOrUpdate(status)
|
||||
} else {
|
||||
schan = Srv.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt)
|
||||
}
|
||||
var schan store.StoreChannel
|
||||
if broadcast {
|
||||
schan = Srv.Store.Status().SaveOrUpdate(status)
|
||||
} else {
|
||||
schan = Srv.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt)
|
||||
}
|
||||
|
||||
if result := <-achan; result.Err != nil {
|
||||
l4g.Error(utils.T("api.status.last_activity.error"), userId, sessionId, result.Err)
|
||||
}
|
||||
if result := <-achan; result.Err != nil {
|
||||
l4g.Error(utils.T("api.status.last_activity.error"), userId, sessionId, result.Err)
|
||||
}
|
||||
|
||||
if result := <-schan; result.Err != nil {
|
||||
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
||||
if result := <-schan; result.Err != nil {
|
||||
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
||||
}
|
||||
}
|
||||
|
||||
if broadcast {
|
||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", "", nil)
|
||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
|
||||
event.Add("status", model.STATUS_ONLINE)
|
||||
event.Add("user_id", status.UserId)
|
||||
go Publish(event)
|
||||
@@ -131,7 +215,7 @@ func SetStatusOffline(userId string, manual bool) {
|
||||
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
||||
}
|
||||
|
||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", "", nil)
|
||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
|
||||
event.Add("status", model.STATUS_OFFLINE)
|
||||
event.Add("user_id", status.UserId)
|
||||
go Publish(event)
|
||||
@@ -168,15 +252,18 @@ func SetStatusAwayIfNeeded(userId string, manual bool) {
|
||||
l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err)
|
||||
}
|
||||
|
||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", "", nil)
|
||||
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
|
||||
event.Add("status", model.STATUS_AWAY)
|
||||
event.Add("user_id", status.UserId)
|
||||
go Publish(event)
|
||||
}
|
||||
|
||||
func GetStatus(userId string) (*model.Status, *model.AppError) {
|
||||
if status, ok := statusCache.Get(userId); ok {
|
||||
return status.(*model.Status), nil
|
||||
if result, ok := statusCache.Get(userId); ok {
|
||||
status := result.(*model.Status)
|
||||
statusCopy := &model.Status{}
|
||||
*statusCopy = *status
|
||||
return statusCopy, nil
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Status().Get(userId); result.Err != nil {
|
||||
@@ -232,6 +319,10 @@ func SetActiveChannel(userId string, channelId string) *model.AppError {
|
||||
status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), channelId}
|
||||
} else {
|
||||
status.ActiveChannel = channelId
|
||||
if !status.Manual {
|
||||
status.Status = model.STATUS_ONLINE
|
||||
}
|
||||
status.LastActivityAt = model.GetMillis()
|
||||
}
|
||||
|
||||
AddStatusCache(status)
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStatuses(t *testing.T) {
|
||||
@@ -59,7 +58,7 @@ func TestStatuses(t *testing.T) {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
WebSocketClient.GetStatuses()
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||
@@ -76,6 +75,7 @@ func TestStatuses(t *testing.T) {
|
||||
}
|
||||
|
||||
if status, ok := resp.Data[th.BasicUser2.Id]; !ok {
|
||||
t.Log(len(resp.Data))
|
||||
t.Fatal("should have had user status")
|
||||
} else if status != model.STATUS_ONLINE {
|
||||
t.Log(status)
|
||||
@@ -83,7 +83,55 @@ func TestStatuses(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
SetStatusAwayIfNeeded(th.BasicUser2.Id, false)
|
||||
WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id})
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
|
||||
for _, status := range resp.Data {
|
||||
if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE {
|
||||
t.Fatal("one of the statuses had an invalid value")
|
||||
}
|
||||
}
|
||||
|
||||
if status, ok := resp.Data[th.BasicUser2.Id]; !ok {
|
||||
t.Log(len(resp.Data))
|
||||
t.Fatal("should have had user status")
|
||||
} else if status != model.STATUS_ONLINE {
|
||||
t.Log(status)
|
||||
t.Fatal("status should have been online")
|
||||
} else if len(resp.Data) != 1 {
|
||||
t.Fatal("only 1 status should be returned")
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"})
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
|
||||
if len(resp.Data) != 2 {
|
||||
t.Fatal("2 statuses should be returned")
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketClient.GetStatusesByIds([]string{})
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
t.Fatal("should have errored - empty user ids")
|
||||
}
|
||||
|
||||
WebSocketClient2.Close()
|
||||
|
||||
SetStatusAwayIfNeeded(th.BasicUser.Id, false)
|
||||
|
||||
awayTimeout := *utils.Cfg.TeamSettings.UserStatusAwayTimeout
|
||||
defer func() {
|
||||
@@ -93,10 +141,9 @@ func TestStatuses(t *testing.T) {
|
||||
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
|
||||
SetStatusAwayIfNeeded(th.BasicUser2.Id, false)
|
||||
SetStatusAwayIfNeeded(th.BasicUser2.Id, false)
|
||||
SetStatusAwayIfNeeded(th.BasicUser.Id, false)
|
||||
SetStatusOnline(th.BasicUser.Id, "junk", false)
|
||||
|
||||
WebSocketClient2.Close()
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
WebSocketClient.GetStatuses()
|
||||
@@ -115,20 +162,17 @@ func TestStatuses(t *testing.T) {
|
||||
stop := make(chan bool)
|
||||
onlineHit := false
|
||||
awayHit := false
|
||||
offlineHit := false
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser2.Id {
|
||||
if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id {
|
||||
status := resp.Data["status"].(string)
|
||||
if status == model.STATUS_ONLINE {
|
||||
onlineHit = true
|
||||
} else if status == model.STATUS_AWAY {
|
||||
awayHit = true
|
||||
} else if status == model.STATUS_OFFLINE {
|
||||
offlineHit = true
|
||||
}
|
||||
}
|
||||
case <-stop:
|
||||
@@ -147,11 +191,40 @@ func TestStatuses(t *testing.T) {
|
||||
if !awayHit {
|
||||
t.Fatal("didn't get away event")
|
||||
}
|
||||
if !offlineHit {
|
||||
t.Fatal("didn't get offline event")
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
WebSocketClient.Close()
|
||||
}
|
||||
|
||||
func TestGetStatusesByIds(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
|
||||
if result, err := Client.GetStatusesByIds([]string{th.BasicUser.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
statuses := result.Data.(map[string]string)
|
||||
if len(statuses) != 1 {
|
||||
t.Fatal("should only have 1 status")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.GetStatusesByIds([]string{th.BasicUser.Id, th.BasicUser2.Id, "junk"}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
statuses := result.Data.(map[string]string)
|
||||
if len(statuses) != 3 {
|
||||
t.Fatal("should have 3 statuses")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := Client.GetStatusesByIds([]string{}); err == nil {
|
||||
t.Fatal("should have errored")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestSetActiveChannel(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
@@ -185,8 +258,9 @@ func TestSetActiveChannel(t *testing.T) {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
status, _ = GetStatus(th.BasicUser.Id)
|
||||
// need to check if offline to catch race
|
||||
need to check if offline to catch race
|
||||
if status.Status != model.STATUS_OFFLINE && status.ActiveChannel != th.BasicChannel.Id {
|
||||
t.Fatal("active channel should be set")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
100
api/team.go
100
api/team.go
@@ -31,9 +31,12 @@ func InitTeam() {
|
||||
BaseRoutes.Teams.Handle("/all_team_listings", ApiUserRequired(GetAllTeamListings)).Methods("GET")
|
||||
BaseRoutes.Teams.Handle("/get_invite_info", ApiAppHandler(getInviteInfo)).Methods("POST")
|
||||
BaseRoutes.Teams.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
|
||||
BaseRoutes.Teams.Handle("/members/{id:[A-Za-z0-9]+}", ApiUserRequired(getMembers)).Methods("GET")
|
||||
|
||||
BaseRoutes.NeedTeam.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/stats", ApiUserRequired(getTeamStats)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/members/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getTeamMembers)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/members/ids", ApiUserRequired(getTeamMembersByIds)).Methods("POST")
|
||||
BaseRoutes.NeedTeam.Handle("/members/{user_id:[A-Za-z0-9]+}", ApiUserRequired(getTeamMember)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/update", ApiUserRequired(updateTeam)).Methods("POST")
|
||||
BaseRoutes.NeedTeam.Handle("/update_member_roles", ApiUserRequired(updateMemberRoles)).Methods("POST")
|
||||
|
||||
@@ -305,7 +308,9 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
|
||||
InvalidateCacheForUser(user.Id)
|
||||
|
||||
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
|
||||
go Publish(model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil))
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
|
||||
message.Add("user_id", user.Id)
|
||||
go Publish(message)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -335,11 +340,10 @@ func LeaveTeam(team *model.Team, user *model.User) *model.AppError {
|
||||
|
||||
for _, channel := range channelMembers.Channels {
|
||||
if channel.Type != model.CHANNEL_DIRECT {
|
||||
Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
|
||||
if result := <-Srv.Store.Channel().RemoveMember(channel.Id, user.Id); result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
|
||||
InvalidateCacheForChannel(channel.Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,6 +893,25 @@ func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func getTeamStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Team().GetMemberCount(c.TeamId); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
stats := &model.TeamStats{}
|
||||
stats.MemberCount = result.Data.(int64)
|
||||
stats.TeamId = c.TeamId
|
||||
w.Write([]byte(stats.ToJson()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !HasPermissionToCurrentTeamContext(c, model.PERMISSION_IMPORT_TEAM) {
|
||||
c.Err = model.NewLocAppError("importTeam", "api.team.import_team.admin.app_error", nil, "userId="+c.Session.UserId)
|
||||
@@ -982,17 +1005,76 @@ func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func getMembers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
id := params["id"]
|
||||
|
||||
if c.Session.GetTeamByTeamId(id) == nil {
|
||||
if !HasPermissionToTeamContext(c, id, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
offset, err := strconv.Atoi(params["offset"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getTeamMembers", "offset")
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(params["limit"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getTeamMembers", "limit")
|
||||
return
|
||||
}
|
||||
|
||||
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||
if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Team().GetMembers(id); result.Err != nil {
|
||||
if result := <-Srv.Store.Team().GetMembers(c.TeamId, offset, limit); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
members := result.Data.([]*model.TeamMember)
|
||||
w.Write([]byte(model.TeamMembersToJson(members)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
|
||||
userId := params["user_id"]
|
||||
if len(userId) < 26 {
|
||||
c.SetInvalidParam("getTeamMember", "user_id")
|
||||
return
|
||||
}
|
||||
|
||||
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||
if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Team().GetMember(c.TeamId, userId); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
member := result.Data.(model.TeamMember)
|
||||
w.Write([]byte(member.ToJson()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
userIds := model.ArrayFromJson(r.Body)
|
||||
if len(userIds) == 0 {
|
||||
c.SetInvalidParam("getTeamMembersByIds", "user_ids")
|
||||
return
|
||||
}
|
||||
|
||||
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||
if !HasPermissionToTeamContext(c, c.TeamId, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.Team().GetMembersByIds(c.TeamId, userIds); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
|
||||
109
api/team_test.go
109
api/team_test.go
@@ -560,14 +560,80 @@ func TestGetMyTeam(t *testing.T) {
|
||||
func TestGetTeamMembers(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
if result, err := th.BasicClient.GetTeamMembers(th.BasicTeam.Id); err != nil {
|
||||
if result, err := th.BasicClient.GetTeamMembers(th.BasicTeam.Id, 0, 100); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
members := result.Data.([]*model.TeamMember)
|
||||
if members == nil {
|
||||
if len(members) == 0 {
|
||||
t.Fatal("should have results")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := th.BasicClient.GetTeamMembers("junk", 0, 100); err == nil {
|
||||
t.Fatal("should have errored - bad team id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTeamMember(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
if result, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, th.BasicUser.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
member := result.Data.(*model.TeamMember)
|
||||
if member == nil {
|
||||
t.Fatal("should be valid")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := th.BasicClient.GetTeamMember("junk", th.BasicUser.Id); err == nil {
|
||||
t.Fatal("should have errored - bad team id")
|
||||
}
|
||||
|
||||
if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, ""); err == nil {
|
||||
t.Fatal("should have errored - blank user id")
|
||||
}
|
||||
|
||||
if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, "junk"); err == nil {
|
||||
t.Fatal("should have errored - bad user id")
|
||||
}
|
||||
|
||||
if _, err := th.BasicClient.GetTeamMember(th.BasicTeam.Id, "12345678901234567890123456"); err == nil {
|
||||
t.Fatal("should have errored - bad user id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTeamMembersByIds(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
if result, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{th.BasicUser.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
member := result.Data.([]*model.TeamMember)[0]
|
||||
if member.UserId != th.BasicUser.Id {
|
||||
t.Fatal("user id did not match")
|
||||
}
|
||||
if member.TeamId != th.BasicTeam.Id {
|
||||
t.Fatal("team id did not match")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{th.BasicUser.Id, th.BasicUser2.Id, model.NewId()}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
members := result.Data.([]*model.TeamMember)
|
||||
if len(members) != 2 {
|
||||
t.Fatal("length should have been 2")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := th.BasicClient.GetTeamMembersByIds("junk", []string{th.BasicUser.Id}); err == nil {
|
||||
t.Fatal("should have errored - bad team id")
|
||||
}
|
||||
|
||||
if _, err := th.BasicClient.GetTeamMembersByIds(th.BasicTeam.Id, []string{}); err == nil {
|
||||
t.Fatal("should have errored - empty user ids")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTeamMemberRoles(t *testing.T) {
|
||||
@@ -632,3 +698,42 @@ func TestUpdateTeamMemberRoles(t *testing.T) {
|
||||
t.Fatal("Should have worked, user is team admin")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTeamStats(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
Client := th.BasicClient
|
||||
|
||||
if result, err := th.SystemAdminClient.GetTeamStats(th.BasicTeam.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if result.Data.(*model.TeamStats).MemberCount != 2 {
|
||||
t.Fatal("wrong count")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := th.SystemAdminClient.GetTeamStats("junk"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if result.Data.(*model.TeamStats).MemberCount != 0 {
|
||||
t.Fatal("wrong count")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := th.SystemAdminClient.GetTeamStats(th.BasicTeam.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if result.Data.(*model.TeamStats).MemberCount != 2 {
|
||||
t.Fatal("wrong count")
|
||||
}
|
||||
}
|
||||
|
||||
user := model.User{Email: "success+" + model.NewId() + "@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||
ruser, _ := Client.CreateUser(&user, "")
|
||||
store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id))
|
||||
|
||||
Client.Login(user.Email, user.Password)
|
||||
|
||||
if _, err := Client.GetTeamStats(th.BasicTeam.Id); err == nil {
|
||||
t.Fatal("should have errored - not on team")
|
||||
}
|
||||
}
|
||||
|
||||
305
api/user.go
305
api/user.go
@@ -53,9 +53,15 @@ func InitUser() {
|
||||
BaseRoutes.Users.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("/me", ApiUserRequired(getMe)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/direct_profiles", ApiUserRequired(getDirectProfiles)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/profiles_for_dm_list/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfilesForDirectMessageList)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfiles)).Methods("GET")
|
||||
BaseRoutes.NeedTeam.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesInTeam)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesInChannel)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/users/not_in_channel/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesNotInChannel)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/search", ApiUserRequired(searchUsers)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("/ids", ApiUserRequired(getProfilesByIds)).Methods("POST")
|
||||
|
||||
BaseRoutes.NeedTeam.Handle("/users/autocomplete", ApiUserRequired(autocompleteUsersInTeam)).Methods("GET")
|
||||
BaseRoutes.NeedChannel.Handle("/users/autocomplete", ApiUserRequired(autocompleteUsersInChannel)).Methods("GET")
|
||||
|
||||
BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET")
|
||||
@@ -270,7 +276,9 @@ func CreateUser(user *model.User) (*model.User, *model.AppError) {
|
||||
ruser.Sanitize(map[string]bool{})
|
||||
|
||||
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
|
||||
go Publish(model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil))
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
|
||||
message.Add("user_id", ruser.Id)
|
||||
go Publish(message)
|
||||
|
||||
return ruser, nil
|
||||
}
|
||||
@@ -379,7 +387,7 @@ func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, v
|
||||
|
||||
func addDirectChannels(teamId string, user *model.User) {
|
||||
var profiles map[string]*model.User
|
||||
if result := <-Srv.Store.User().GetProfiles(teamId); result.Err != nil {
|
||||
if result := <-Srv.Store.User().GetProfiles(teamId, 0, 100); result.Err != nil {
|
||||
l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error())
|
||||
return
|
||||
} else {
|
||||
@@ -875,7 +883,6 @@ func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
uchan := Srv.Store.User().Get(c.Session.UserId)
|
||||
pchan := Srv.Store.Preference().GetAll(c.Session.UserId)
|
||||
tchan := Srv.Store.Team().GetTeamsByUserId(c.Session.UserId)
|
||||
dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId)
|
||||
|
||||
il.TeamMembers = c.Session.TeamMembers
|
||||
|
||||
@@ -904,19 +911,6 @@ func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
team.Sanitize()
|
||||
}
|
||||
}
|
||||
|
||||
if dp := <-dpchan; dp.Err != nil {
|
||||
c.Err = dp.Err
|
||||
return
|
||||
} else {
|
||||
profiles := dp.Data.(map[string]*model.User)
|
||||
|
||||
for k, p := range profiles {
|
||||
profiles[k] = sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
il.DirectProfiles = profiles
|
||||
}
|
||||
}
|
||||
|
||||
if cchan != nil {
|
||||
@@ -960,25 +954,27 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func getProfilesForDirectMessageList(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
id := params["id"]
|
||||
|
||||
var pchan store.StoreChannel
|
||||
|
||||
if *utils.Cfg.TeamSettings.RestrictDirectMessage == model.DIRECT_MESSAGE_TEAM {
|
||||
if c.Session.GetTeamByTeamId(id) == nil {
|
||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pchan = Srv.Store.User().GetProfiles(id)
|
||||
} else {
|
||||
pchan = Srv.Store.User().GetAllProfiles()
|
||||
offset, err := strconv.Atoi(params["offset"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfiles", "offset")
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-pchan; result.Err != nil {
|
||||
limit, err := strconv.Atoi(params["limit"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfiles", "limit")
|
||||
return
|
||||
}
|
||||
|
||||
etag := (<-Srv.Store.User().GetEtagForAllProfiles()).Data.(string)
|
||||
if HandleEtag(etag, w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.User().GetAllProfiles(offset, limit); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
@@ -988,26 +984,39 @@ func getProfilesForDirectMessageList(c *Context, w http.ResponseWriter, r *http.
|
||||
profiles[k] = sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||
}
|
||||
}
|
||||
|
||||
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
func getProfilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
id := params["id"]
|
||||
teamId := params["team_id"]
|
||||
|
||||
if c.Session.GetTeamByTeamId(id) == nil {
|
||||
if c.Session.GetTeamByTeamId(teamId) == nil {
|
||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
etag := (<-Srv.Store.User().GetEtagForProfiles(id)).Data.(string)
|
||||
offset, err := strconv.Atoi(params["offset"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfilesInTeam", "offset")
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(params["limit"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfilesInTeam", "limit")
|
||||
return
|
||||
}
|
||||
|
||||
etag := (<-Srv.Store.User().GetEtagForProfiles(teamId)).Data.(string)
|
||||
if HandleEtag(etag, w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.User().GetProfiles(id); result.Err != nil {
|
||||
if result := <-Srv.Store.User().GetProfiles(teamId, offset, limit); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
@@ -1022,13 +1031,73 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func getDirectProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
etag := (<-Srv.Store.User().GetEtagForDirectProfiles(c.Session.UserId)).Data.(string)
|
||||
if HandleEtag(etag, w, r) {
|
||||
func getProfilesInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
channelId := params["channel_id"]
|
||||
|
||||
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.User().GetDirectProfiles(c.Session.UserId); result.Err != nil {
|
||||
offset, err := strconv.Atoi(params["offset"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfiles", "offset")
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(params["limit"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfiles", "limit")
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.User().GetProfilesInChannel(channelId, offset, limit, false); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
profiles := result.Data.(map[string]*model.User)
|
||||
|
||||
for k, p := range profiles {
|
||||
profiles[k] = sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||
}
|
||||
}
|
||||
|
||||
func getProfilesNotInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
channelId := params["channel_id"]
|
||||
|
||||
if c.Session.GetTeamByTeamId(c.TeamId) == nil {
|
||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||
return
|
||||
}
|
||||
|
||||
offset, err := strconv.Atoi(params["offset"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfiles", "offset")
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(params["limit"])
|
||||
if err != nil {
|
||||
c.SetInvalidParam("getProfiles", "limit")
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.User().GetProfilesNotInChannel(c.TeamId, channelId, offset, limit); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
@@ -1038,7 +1107,6 @@ func getDirectProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
profiles[k] = sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||
}
|
||||
}
|
||||
@@ -2522,3 +2590,152 @@ func sanitizeProfile(c *Context, user *model.User) *model.User {
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
props := model.MapFromJson(r.Body)
|
||||
|
||||
term := props["term"]
|
||||
if len(term) == 0 {
|
||||
c.SetInvalidParam("searchUsers", "term")
|
||||
return
|
||||
}
|
||||
|
||||
teamId := props["team_id"]
|
||||
inChannelId := props["in_channel"]
|
||||
notInChannelId := props["not_in_channel"]
|
||||
|
||||
if inChannelId != "" && !HasPermissionToChannelContext(c, inChannelId, model.PERMISSION_READ_CHANNEL) {
|
||||
return
|
||||
}
|
||||
|
||||
if notInChannelId != "" && !HasPermissionToChannelContext(c, notInChannelId, model.PERMISSION_READ_CHANNEL) {
|
||||
return
|
||||
}
|
||||
|
||||
var uchan store.StoreChannel
|
||||
if inChannelId != "" {
|
||||
uchan = Srv.Store.User().SearchInChannel(inChannelId, term, store.USER_SEARCH_TYPE_USERNAME)
|
||||
} else if notInChannelId != "" {
|
||||
uchan = Srv.Store.User().SearchNotInChannel(teamId, notInChannelId, term, store.USER_SEARCH_TYPE_USERNAME)
|
||||
} else {
|
||||
uchan = Srv.Store.User().Search(teamId, term, store.USER_SEARCH_TYPE_USERNAME)
|
||||
}
|
||||
|
||||
if result := <-uchan; result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
profiles := result.Data.([]*model.User)
|
||||
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(model.UserListToJson(profiles)))
|
||||
}
|
||||
}
|
||||
|
||||
func getProfilesByIds(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
userIds := model.ArrayFromJson(r.Body)
|
||||
|
||||
if len(userIds) == 0 {
|
||||
c.SetInvalidParam("getProfilesByIds", "user_ids")
|
||||
return
|
||||
}
|
||||
|
||||
if result := <-Srv.Store.User().GetProfileByIds(userIds); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
profiles := result.Data.(map[string]*model.User)
|
||||
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(model.UserMapToJson(profiles)))
|
||||
}
|
||||
}
|
||||
|
||||
func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
channelId := params["channel_id"]
|
||||
teamId := params["team_id"]
|
||||
|
||||
term := r.URL.Query().Get("term")
|
||||
|
||||
if c.Session.GetTeamByTeamId(teamId) == nil {
|
||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||
return
|
||||
}
|
||||
|
||||
uchan := Srv.Store.User().SearchInChannel(channelId, term, store.USER_SEARCH_TYPE_ALL)
|
||||
nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, store.USER_SEARCH_TYPE_ALL)
|
||||
|
||||
autocomplete := &model.UserAutocompleteInChannel{}
|
||||
|
||||
if result := <-uchan; result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
profiles := result.Data.([]*model.User)
|
||||
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
autocomplete.InChannel = profiles
|
||||
}
|
||||
|
||||
if result := <-nuchan; result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
profiles := result.Data.([]*model.User)
|
||||
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
autocomplete.OutOfChannel = profiles
|
||||
}
|
||||
|
||||
w.Write([]byte(autocomplete.ToJson()))
|
||||
}
|
||||
|
||||
func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
teamId := params["team_id"]
|
||||
|
||||
term := r.URL.Query().Get("term")
|
||||
|
||||
if c.Session.GetTeamByTeamId(teamId) == nil {
|
||||
if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uchan := Srv.Store.User().Search(teamId, term, store.USER_SEARCH_TYPE_ALL)
|
||||
|
||||
autocomplete := &model.UserAutocompleteInTeam{}
|
||||
|
||||
if result := <-uchan; result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
profiles := result.Data.([]*model.User)
|
||||
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
autocomplete.InTeam = profiles
|
||||
}
|
||||
|
||||
w.Write([]byte(autocomplete.ToJson()))
|
||||
}
|
||||
|
||||
514
api/user_test.go
514
api/user_test.go
@@ -435,7 +435,7 @@ func TestGetUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if userMap, err := Client.GetProfiles(rteam.Data.(*model.Team).Id, ""); err != nil {
|
||||
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(userMap.Data.(map[string]*model.User)) != 2 {
|
||||
t.Fatal("should have been 2")
|
||||
@@ -444,7 +444,7 @@ func TestGetUser(t *testing.T) {
|
||||
} else {
|
||||
|
||||
// test etag caching
|
||||
if cache_result, err := Client.GetProfiles(rteam.Data.(*model.Team).Id, userMap.Etag); err != nil {
|
||||
if cache_result, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 100, userMap.Etag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(map[string]*model.User) != nil {
|
||||
t.Log(cache_result.Data)
|
||||
@@ -452,7 +452,25 @@ func TestGetUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := Client.GetProfiles(rteam2.Data.(*model.Team).Id, ""); err == nil {
|
||||
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 0, 1, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(userMap.Data.(map[string]*model.User)) != 1 {
|
||||
t.Fatal("should have been 1")
|
||||
}
|
||||
|
||||
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 1, 1, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(userMap.Data.(map[string]*model.User)) != 1 {
|
||||
t.Fatal("should have been 1")
|
||||
}
|
||||
|
||||
if userMap, err := Client.GetProfilesInTeam(rteam.Data.(*model.Team).Id, 10, 10, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(userMap.Data.(map[string]*model.User)) != 0 {
|
||||
t.Fatal("should have been 0")
|
||||
}
|
||||
|
||||
if _, err := Client.GetProfilesInTeam(rteam2.Data.(*model.Team).Id, 0, 100, ""); err == nil {
|
||||
t.Fatal("shouldn't have access")
|
||||
}
|
||||
|
||||
@@ -468,12 +486,12 @@ func TestGetUser(t *testing.T) {
|
||||
|
||||
Client.Login(user.Email, "passwd1")
|
||||
|
||||
if _, err := Client.GetProfiles(rteam2.Data.(*model.Team).Id, ""); err != nil {
|
||||
if _, err := Client.GetProfilesInTeam(rteam2.Data.(*model.Team).Id, 0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDirectProfiles(t *testing.T) {
|
||||
func TestGetProfiles(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id))
|
||||
@@ -485,62 +503,7 @@ func TestGetDirectProfiles(t *testing.T) {
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||
|
||||
if result, err := th.BasicClient.GetDirectProfiles(""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) != 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
if users[th.BasicUser2.Id] == nil {
|
||||
t.Fatal("missing expected user")
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if user.Email == "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||
|
||||
if result, err := th.BasicClient.GetDirectProfiles(""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) != 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
if users[th.BasicUser2.Id] == nil {
|
||||
t.Fatal("missing expected user")
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if user.Email != "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProfilesForDirectMessageList(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
th.BasicClient.Must(th.BasicClient.CreateDirectChannel(th.BasicUser2.Id))
|
||||
|
||||
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||
defer func() {
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
||||
}()
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||
|
||||
if result, err := th.BasicClient.GetProfilesForDirectMessageList(th.BasicTeam.Id); err != nil {
|
||||
if result, err := th.BasicClient.GetProfiles(0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
@@ -554,11 +517,20 @@ func TestGetProfilesForDirectMessageList(t *testing.T) {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
}
|
||||
|
||||
// test etag caching
|
||||
if cache_result, err := th.BasicClient.GetProfiles(0, 100, result.Etag); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache_result.Data.(map[string]*model.User) != nil {
|
||||
t.Log(cache_result.Etag)
|
||||
t.Log(result.Etag)
|
||||
t.Fatal("cache should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||
|
||||
if result, err := th.BasicClient.GetProfilesForDirectMessageList(th.BasicTeam.Id); err != nil {
|
||||
if result, err := th.BasicClient.GetProfiles(0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
@@ -575,6 +547,61 @@ func TestGetProfilesForDirectMessageList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProfilesByIds(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
|
||||
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||
defer func() {
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
||||
}()
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||
|
||||
if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) != 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if user.Email == "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||
|
||||
if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) != 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if user.Email != "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := th.BasicClient.GetProfilesByIds([]string{th.BasicUser.Id, th.BasicUser2.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) != 2 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAudits(t *testing.T) {
|
||||
th := Setup()
|
||||
Client := th.CreateClient()
|
||||
@@ -1837,3 +1864,366 @@ func TestUserTyping(t *testing.T) {
|
||||
t.Fatal("did not receive typing event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProfilesInChannel(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
|
||||
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||
defer func() {
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
||||
}()
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||
|
||||
if result, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) < 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if user.Email == "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th.LoginBasic2()
|
||||
|
||||
if _, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||
t.Fatal("should not have access")
|
||||
}
|
||||
|
||||
Client.Must(Client.JoinChannel(th.BasicChannel.Id))
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||
|
||||
if result, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) < 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, user := range users {
|
||||
if user.Email != "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
if user.Id == th.BasicUser2.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("should have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||
Client.Must(Client.CreateUser(&user, ""))
|
||||
|
||||
Client.Login(user.Email, "passwd1")
|
||||
Client.SetTeamId("junk")
|
||||
|
||||
if _, err := Client.GetProfilesInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||
t.Fatal("should not have access")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProfilesNotInChannel(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
|
||||
prevShowEmail := utils.Cfg.PrivacySettings.ShowEmailAddress
|
||||
defer func() {
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = prevShowEmail
|
||||
}()
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = true
|
||||
|
||||
if result, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) < 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, user := range users {
|
||||
if user.Email == "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
if user.Id == th.BasicUser2.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("should have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
user := &model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||
LinkUserToTeam(user, th.BasicTeam)
|
||||
|
||||
th.LoginBasic2()
|
||||
|
||||
if _, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||
t.Fatal("should not have access")
|
||||
}
|
||||
|
||||
Client.Must(Client.JoinChannel(th.BasicChannel.Id))
|
||||
|
||||
utils.Cfg.PrivacySettings.ShowEmailAddress = false
|
||||
|
||||
if result, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.(map[string]*model.User)
|
||||
|
||||
if len(users) < 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, user := range users {
|
||||
if user.Email != "" {
|
||||
t.Fatal("problem with show email")
|
||||
}
|
||||
if user.Id == th.BasicUser2.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
t.Fatal("should not have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||
Client.Must(Client.CreateUser(&user2, ""))
|
||||
|
||||
Client.Login(user2.Email, "passwd1")
|
||||
Client.SetTeamId(th.BasicTeam.Id)
|
||||
|
||||
if _, err := Client.GetProfilesNotInChannel(th.BasicChannel.Id, 0, 100, ""); err == nil {
|
||||
t.Fatal("should not have access")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchUsers(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
|
||||
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
found := false
|
||||
for _, user := range users {
|
||||
if user.Id == th.BasicUser.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("should have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"in_channel": th.BasicChannel.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
if len(users) != 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, user := range users {
|
||||
if user.Id == th.BasicUser.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("should have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.SearchUsers(th.BasicUser2.Username, "", map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
if len(users) != 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
found1 := false
|
||||
found2 := false
|
||||
for _, user := range users {
|
||||
if user.Id == th.BasicUser.Id {
|
||||
found1 = true
|
||||
} else if user.Id == th.BasicUser2.Id {
|
||||
found2 = true
|
||||
}
|
||||
}
|
||||
|
||||
if found1 {
|
||||
t.Fatal("should not have found profile")
|
||||
}
|
||||
if !found2 {
|
||||
t.Fatal("should have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.SearchUsers(th.BasicUser2.Username, th.BasicTeam.Id, map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
if len(users) != 1 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
|
||||
found1 := false
|
||||
found2 := false
|
||||
for _, user := range users {
|
||||
if user.Id == th.BasicUser.Id {
|
||||
found1 = true
|
||||
} else if user.Id == th.BasicUser2.Id {
|
||||
found2 = true
|
||||
}
|
||||
}
|
||||
|
||||
if found1 {
|
||||
t.Fatal("should not have found profile")
|
||||
}
|
||||
if !found2 {
|
||||
t.Fatal("should have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.SearchUsers(th.BasicUser.Username, "junk", map[string]string{"not_in_channel": th.BasicChannel.Id}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
if len(users) != 0 {
|
||||
t.Fatal("map was wrong length")
|
||||
}
|
||||
}
|
||||
|
||||
th.LoginBasic2()
|
||||
|
||||
if result, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
found := false
|
||||
for _, user := range users {
|
||||
if user.Id == th.BasicUser.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("should have found profile")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := Client.SearchUsers("", "", map[string]string{}); err == nil {
|
||||
t.Fatal("should have errored - blank term")
|
||||
}
|
||||
|
||||
if _, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"in_channel": th.BasicChannel.Id}); err == nil {
|
||||
t.Fatal("should not have access")
|
||||
}
|
||||
|
||||
if _, err := Client.SearchUsers(th.BasicUser.Username, "", map[string]string{"not_in_channel": th.BasicChannel.Id}); err == nil {
|
||||
t.Fatal("should not have access")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutocompleteUsers(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
Client := th.BasicClient
|
||||
|
||||
if result, err := Client.AutocompleteUsersInTeam(th.BasicUser.Username); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
autocomplete := result.Data.(*model.UserAutocompleteInTeam)
|
||||
if len(autocomplete.InTeam) != 1 {
|
||||
t.Fatal("should have returned 1 user in")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.AutocompleteUsersInTeam(th.BasicUser.Username[0:5]); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
autocomplete := result.Data.(*model.UserAutocompleteInTeam)
|
||||
if len(autocomplete.InTeam) < 1 {
|
||||
t.Fatal("should have returned at least 1 user in")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.AutocompleteUsersInChannel(th.BasicUser.Username, th.BasicChannel.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
autocomplete := result.Data.(*model.UserAutocompleteInChannel)
|
||||
if len(autocomplete.InChannel) != 1 {
|
||||
t.Fatal("should have returned 1 user in")
|
||||
}
|
||||
if len(autocomplete.OutOfChannel) != 0 {
|
||||
t.Fatal("should have returned no users out")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.AutocompleteUsersInChannel("", th.BasicChannel.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
autocomplete := result.Data.(*model.UserAutocompleteInChannel)
|
||||
if len(autocomplete.InChannel) != 1 && autocomplete.InChannel[0].Id != th.BasicUser2.Id {
|
||||
t.Fatal("should have returned at 1 user in")
|
||||
}
|
||||
if len(autocomplete.OutOfChannel) != 1 && autocomplete.OutOfChannel[0].Id != th.BasicUser2.Id {
|
||||
t.Fatal("should have returned 1 user out")
|
||||
}
|
||||
}
|
||||
|
||||
if result, err := Client.AutocompleteUsersInTeam(""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
autocomplete := result.Data.(*model.UserAutocompleteInTeam)
|
||||
if len(autocomplete.InTeam) != 2 {
|
||||
t.Fatal("should have returned 2 users in")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := Client.AutocompleteUsersInChannel("", "junk"); err == nil {
|
||||
t.Fatal("should have errored - bad channel id")
|
||||
}
|
||||
|
||||
Client.SetTeamId("junk")
|
||||
if _, err := Client.AutocompleteUsersInChannel("", th.BasicChannel.Id); err == nil {
|
||||
t.Fatal("should have errored - bad team id")
|
||||
}
|
||||
|
||||
if _, err := Client.AutocompleteUsersInTeam(""); err == nil {
|
||||
t.Fatal("should have errored - bad team id")
|
||||
}
|
||||
}
|
||||
|
||||
155
api/web_conn.go
155
api/web_conn.go
@@ -4,54 +4,52 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/platform/model"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/gorilla/websocket"
|
||||
goi18n "github.com/nicksnyder/go-i18n/i18n"
|
||||
)
|
||||
|
||||
const (
|
||||
WRITE_WAIT = 10 * time.Second
|
||||
PONG_WAIT = 60 * time.Second
|
||||
PING_PERIOD = (PONG_WAIT * 9) / 10
|
||||
MAX_SIZE = 512
|
||||
REDIS_WAIT = 60 * time.Second
|
||||
WRITE_WAIT = 30 * time.Second
|
||||
PONG_WAIT = 100 * time.Second
|
||||
PING_PERIOD = (PONG_WAIT * 6) / 10
|
||||
)
|
||||
|
||||
type WebConn struct {
|
||||
WebSocket *websocket.Conn
|
||||
Send chan model.WebSocketMessage
|
||||
SessionToken string
|
||||
UserId string
|
||||
T goi18n.TranslateFunc
|
||||
Locale string
|
||||
isMemberOfChannel map[string]bool
|
||||
isMemberOfTeam map[string]bool
|
||||
WebSocket *websocket.Conn
|
||||
Send chan model.WebSocketMessage
|
||||
SessionToken string
|
||||
UserId string
|
||||
T goi18n.TranslateFunc
|
||||
Locale string
|
||||
AllChannelMembers map[string]string
|
||||
LastAllChannelMembersTime int64
|
||||
}
|
||||
|
||||
func NewWebConn(c *Context, ws *websocket.Conn) *WebConn {
|
||||
go SetStatusOnline(c.Session.UserId, c.Session.Id, false)
|
||||
|
||||
return &WebConn{
|
||||
Send: make(chan model.WebSocketMessage, 64),
|
||||
WebSocket: ws,
|
||||
UserId: c.Session.UserId,
|
||||
SessionToken: c.Session.Token,
|
||||
T: c.T,
|
||||
Locale: c.Locale,
|
||||
isMemberOfChannel: make(map[string]bool),
|
||||
isMemberOfTeam: make(map[string]bool),
|
||||
Send: make(chan model.WebSocketMessage, 256),
|
||||
WebSocket: ws,
|
||||
UserId: c.Session.UserId,
|
||||
SessionToken: c.Session.Token,
|
||||
T: c.T,
|
||||
Locale: c.Locale,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WebConn) readPump() {
|
||||
defer func() {
|
||||
hub.Unregister(c)
|
||||
HubUnregister(c)
|
||||
c.WebSocket.Close()
|
||||
}()
|
||||
c.WebSocket.SetReadLimit(MAX_SIZE)
|
||||
c.WebSocket.SetReadLimit(SOCKET_MAX_MESSAGE_SIZE_KB)
|
||||
c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
|
||||
c.WebSocket.SetPongHandler(func(string) error {
|
||||
c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
|
||||
@@ -62,6 +60,13 @@ func (c *WebConn) readPump() {
|
||||
for {
|
||||
var req model.WebSocketRequest
|
||||
if err := c.WebSocket.ReadJSON(&req); err != nil {
|
||||
// browsers will appear as CloseNoStatusReceived
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||
l4g.Debug(fmt.Sprintf("websocket.read: client side closed socket userId=%v", c.UserId))
|
||||
} else {
|
||||
l4g.Debug(fmt.Sprintf("websocket.read: cannot read, closing websocket for userId=%v error=%v", c.UserId, err.Error()))
|
||||
}
|
||||
|
||||
return
|
||||
} else {
|
||||
BaseRoutes.WebSocket.ServeWebSocket(c, &req)
|
||||
@@ -87,63 +92,97 @@ func (c *WebConn) writePump() {
|
||||
}
|
||||
|
||||
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
||||
if err := c.WebSocket.WriteJSON(msg); err != nil {
|
||||
if err := c.WebSocket.WriteMessage(websocket.TextMessage, msg.GetPreComputeJson()); err != nil {
|
||||
// browsers will appear as CloseNoStatusReceived
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||
l4g.Debug(fmt.Sprintf("websocket.send: client side closed socket userId=%v", c.UserId))
|
||||
} else {
|
||||
l4g.Debug(fmt.Sprintf("websocket.send: cannot send, closing websocket for userId=%v, error=%v", c.UserId, err.Error()))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
|
||||
if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||
// browsers will appear as CloseNoStatusReceived
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||
l4g.Debug(fmt.Sprintf("websocket.ticker: client side closed socket userId=%v", c.UserId))
|
||||
} else {
|
||||
l4g.Debug(fmt.Sprintf("websocket.ticker: cannot read, closing websocket for userId=%v error=%v", c.UserId, err.Error()))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WebConn) InvalidateCache() {
|
||||
c.isMemberOfTeam = make(map[string]bool)
|
||||
c.isMemberOfChannel = make(map[string]bool)
|
||||
func (webCon *WebConn) InvalidateCache() {
|
||||
webCon.AllChannelMembers = nil
|
||||
webCon.LastAllChannelMembersTime = 0
|
||||
|
||||
}
|
||||
|
||||
func (c *WebConn) InvalidateCacheForChannel(channelId string) {
|
||||
delete(c.isMemberOfChannel, channelId)
|
||||
}
|
||||
func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
|
||||
// If the event is destined to a specific user
|
||||
if len(msg.Broadcast.UserId) > 0 && webCon.UserId != msg.Broadcast.UserId {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *WebConn) IsMemberOfTeam(teamId string) bool {
|
||||
isMember, ok := c.isMemberOfTeam[teamId]
|
||||
if !ok {
|
||||
session := GetSession(c.SessionToken)
|
||||
if session == nil {
|
||||
isMember = false
|
||||
c.isMemberOfTeam[teamId] = isMember
|
||||
} else {
|
||||
member := session.GetTeamByTeamId(teamId)
|
||||
// if the user is omitted don't send the message
|
||||
if len(msg.Broadcast.OmitUsers) > 0 {
|
||||
if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if member != nil {
|
||||
isMember = true
|
||||
c.isMemberOfTeam[teamId] = isMember
|
||||
// Only report events to users who are in the channel for the event
|
||||
if len(msg.Broadcast.ChannelId) > 0 {
|
||||
|
||||
if model.GetMillis()-webCon.LastAllChannelMembersTime > 1000*60*15 { // 15 minutes
|
||||
webCon.AllChannelMembers = nil
|
||||
webCon.LastAllChannelMembersTime = 0
|
||||
}
|
||||
|
||||
if webCon.AllChannelMembers == nil {
|
||||
if result := <-Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true); result.Err != nil {
|
||||
l4g.Error("webhub.shouldSendEvent: " + result.Err.Error())
|
||||
return false
|
||||
} else {
|
||||
isMember = true
|
||||
c.isMemberOfTeam[teamId] = isMember
|
||||
webCon.AllChannelMembers = result.Data.(map[string]string)
|
||||
webCon.LastAllChannelMembersTime = model.GetMillis()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return isMember
|
||||
}
|
||||
|
||||
func (c *WebConn) IsMemberOfChannel(channelId string) bool {
|
||||
isMember, ok := c.isMemberOfChannel[channelId]
|
||||
if !ok {
|
||||
if cresult := <-Srv.Store.Channel().GetMember(channelId, c.UserId); cresult.Err != nil {
|
||||
isMember = false
|
||||
c.isMemberOfChannel[channelId] = isMember
|
||||
if _, ok := webCon.AllChannelMembers[msg.Broadcast.ChannelId]; ok {
|
||||
return true
|
||||
} else {
|
||||
isMember = true
|
||||
c.isMemberOfChannel[channelId] = isMember
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return isMember
|
||||
// Only report events to users who are in the team for the event
|
||||
if len(msg.Broadcast.TeamId) > 0 {
|
||||
return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (webCon *WebConn) IsMemberOfTeam(teamId string) bool {
|
||||
session := GetSession(webCon.SessionToken)
|
||||
if session == nil {
|
||||
return false
|
||||
} else {
|
||||
member := session.GetTeamByTeamId(teamId)
|
||||
|
||||
if member != nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
152
api/web_hub.go
152
api/web_hub.go
@@ -5,6 +5,8 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"runtime"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
|
||||
@@ -14,27 +16,77 @@ import (
|
||||
)
|
||||
|
||||
type Hub struct {
|
||||
connections map[*WebConn]bool
|
||||
register chan *WebConn
|
||||
unregister chan *WebConn
|
||||
broadcast chan *model.WebSocketEvent
|
||||
stop chan string
|
||||
invalidateUser chan string
|
||||
invalidateChannel chan string
|
||||
connections map[*WebConn]bool
|
||||
register chan *WebConn
|
||||
unregister chan *WebConn
|
||||
broadcast chan *model.WebSocketEvent
|
||||
stop chan string
|
||||
invalidateUser chan string
|
||||
}
|
||||
|
||||
var hub = &Hub{
|
||||
register: make(chan *WebConn),
|
||||
unregister: make(chan *WebConn),
|
||||
connections: make(map[*WebConn]bool),
|
||||
broadcast: make(chan *model.WebSocketEvent),
|
||||
stop: make(chan string),
|
||||
invalidateUser: make(chan string),
|
||||
invalidateChannel: make(chan string),
|
||||
var hubs []*Hub = make([]*Hub, 0)
|
||||
|
||||
func NewWebHub() *Hub {
|
||||
return &Hub{
|
||||
register: make(chan *WebConn),
|
||||
unregister: make(chan *WebConn),
|
||||
connections: make(map[*WebConn]bool, model.SESSION_CACHE_SIZE),
|
||||
broadcast: make(chan *model.WebSocketEvent, 4096),
|
||||
stop: make(chan string),
|
||||
invalidateUser: make(chan string),
|
||||
}
|
||||
}
|
||||
|
||||
func TotalWebsocketConnections() int {
|
||||
// XXX TODO FIXME, this is racy and needs to be fixed
|
||||
count := 0
|
||||
for _, hub := range hubs {
|
||||
count = count + len(hub.connections)
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func HubStart() {
|
||||
l4g.Info(utils.T("api.web_hub.start.starting.debug"), runtime.NumCPU()*2)
|
||||
|
||||
// Total number of hubs is twice the number of CPUs.
|
||||
hubs = make([]*Hub, runtime.NumCPU()*2)
|
||||
|
||||
for i := 0; i < len(hubs); i++ {
|
||||
hubs[i] = NewWebHub()
|
||||
hubs[i].Start()
|
||||
}
|
||||
}
|
||||
|
||||
func HubStop() {
|
||||
l4g.Info(utils.T("api.web_hub.start.stopping.debug"))
|
||||
|
||||
for _, hub := range hubs {
|
||||
hub.Stop()
|
||||
}
|
||||
|
||||
hubs = make([]*Hub, 0)
|
||||
}
|
||||
|
||||
func HubRegister(webConn *WebConn) {
|
||||
hash := fnv.New32a()
|
||||
hash.Write([]byte(webConn.UserId))
|
||||
index := hash.Sum32() % uint32(len(hubs))
|
||||
hubs[index].Register(webConn)
|
||||
}
|
||||
|
||||
func HubUnregister(webConn *WebConn) {
|
||||
for _, hub := range hubs {
|
||||
hub.Unregister(webConn)
|
||||
}
|
||||
}
|
||||
|
||||
func Publish(message *model.WebSocketEvent) {
|
||||
hub.Broadcast(message)
|
||||
message.DoPreComputeJson()
|
||||
for _, hub := range hubs {
|
||||
hub.Broadcast(message)
|
||||
}
|
||||
|
||||
if einterfaces.GetClusterInterface() != nil {
|
||||
einterfaces.GetClusterInterface().Publish(message)
|
||||
@@ -42,11 +94,19 @@ func Publish(message *model.WebSocketEvent) {
|
||||
}
|
||||
|
||||
func PublishSkipClusterSend(message *model.WebSocketEvent) {
|
||||
hub.Broadcast(message)
|
||||
message.DoPreComputeJson()
|
||||
for _, hub := range hubs {
|
||||
hub.Broadcast(message)
|
||||
}
|
||||
}
|
||||
|
||||
func InvalidateCacheForUser(userId string) {
|
||||
hub.invalidateUser <- userId
|
||||
|
||||
Srv.Store.Channel().InvalidateAllChannelMembersForUser(userId)
|
||||
|
||||
for _, hub := range hubs {
|
||||
hub.InvalidateUser(userId)
|
||||
}
|
||||
|
||||
if einterfaces.GetClusterInterface() != nil {
|
||||
einterfaces.GetClusterInterface().InvalidateCacheForUser(userId)
|
||||
@@ -54,11 +114,17 @@ func InvalidateCacheForUser(userId string) {
|
||||
}
|
||||
|
||||
func InvalidateCacheForChannel(channelId string) {
|
||||
hub.invalidateChannel <- channelId
|
||||
|
||||
if einterfaces.GetClusterInterface() != nil {
|
||||
einterfaces.GetClusterInterface().InvalidateCacheForChannel(channelId)
|
||||
}
|
||||
// XXX TODO FIX ME
|
||||
// This can be removed, but the performance branch
|
||||
// needs to be merged into master so it can be removed
|
||||
// from the enterprise repo as well.
|
||||
|
||||
// hub.invalidateChannel <- channelId
|
||||
|
||||
// if einterfaces.GetClusterInterface() != nil {
|
||||
// einterfaces.GetClusterInterface().InvalidateCacheForChannel(channelId)
|
||||
// }
|
||||
}
|
||||
|
||||
func (h *Hub) Register(webConn *WebConn) {
|
||||
@@ -79,6 +145,10 @@ func (h *Hub) Broadcast(message *model.WebSocketEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hub) InvalidateUser(userId string) {
|
||||
h.invalidateUser <- userId
|
||||
}
|
||||
|
||||
func (h *Hub) Stop() {
|
||||
h.stop <- "all"
|
||||
}
|
||||
@@ -108,6 +178,7 @@ func (h *Hub) Start() {
|
||||
if !found {
|
||||
go SetStatusOffline(userId, false)
|
||||
}
|
||||
|
||||
case userId := <-h.invalidateUser:
|
||||
for webCon := range h.connections {
|
||||
if webCon.UserId == userId {
|
||||
@@ -115,26 +186,20 @@ func (h *Hub) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
case channelId := <-h.invalidateChannel:
|
||||
for webCon := range h.connections {
|
||||
webCon.InvalidateCacheForChannel(channelId)
|
||||
}
|
||||
|
||||
case msg := <-h.broadcast:
|
||||
for webCon := range h.connections {
|
||||
if shouldSendEvent(webCon, msg) {
|
||||
if webCon.ShouldSendEvent(msg) {
|
||||
select {
|
||||
case webCon.Send <- msg:
|
||||
default:
|
||||
l4g.Error(fmt.Sprintf("webhub.broadcast: cannot send, closing websocket for userId=%v", webCon.UserId))
|
||||
close(webCon.Send)
|
||||
delete(h.connections, webCon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case s := <-h.stop:
|
||||
l4g.Debug(utils.T("api.web_hub.start.stopping.debug"), s)
|
||||
|
||||
case <-h.stop:
|
||||
for webCon := range h.connections {
|
||||
webCon.WebSocket.Close()
|
||||
}
|
||||
@@ -144,28 +209,3 @@ func (h *Hub) Start() {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func shouldSendEvent(webCon *WebConn, msg *model.WebSocketEvent) bool {
|
||||
// If the event is destined to a specific user
|
||||
if len(msg.Broadcast.UserId) > 0 && webCon.UserId != msg.Broadcast.UserId {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the user is omitted don't send the message
|
||||
if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only report events to users who are in the channel for the event
|
||||
if len(msg.Broadcast.ChannelId) > 0 {
|
||||
return webCon.IsMemberOfChannel(msg.Broadcast.ChannelId)
|
||||
}
|
||||
|
||||
// Only report events to users who are in the team for the event
|
||||
if len(msg.Broadcast.TeamId) > 0 {
|
||||
return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -11,16 +11,20 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
SOCKET_MAX_MESSAGE_SIZE_KB = 8 * 1024 // 8KB
|
||||
)
|
||||
|
||||
func InitWebSocket() {
|
||||
l4g.Debug(utils.T("api.web_socket.init.debug"))
|
||||
BaseRoutes.Users.Handle("/websocket", ApiUserRequiredTrustRequester(connect)).Methods("GET")
|
||||
hub.Start()
|
||||
HubStart()
|
||||
}
|
||||
|
||||
func connect(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
upgrader := websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
ReadBufferSize: SOCKET_MAX_MESSAGE_SIZE_KB,
|
||||
WriteBufferSize: SOCKET_MAX_MESSAGE_SIZE_KB,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
@@ -34,7 +38,7 @@ func connect(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
wc := NewWebConn(c, ws)
|
||||
hub.Register(wc)
|
||||
HubRegister(wc)
|
||||
go wc.writePump()
|
||||
wc.readPump()
|
||||
}
|
||||
|
||||
@@ -31,11 +31,17 @@ func (wh *webSocketHandler) ServeWebSocket(conn *WebConn, r *model.WebSocketRequ
|
||||
if data, err = wh.handlerFunc(r); err != nil {
|
||||
l4g.Error(utils.T("api.web_socket_handler.log.error"), "/api/v3/users/websocket", r.Action, r.Seq, r.Session.UserId, err.SystemMessage(utils.T), err.DetailedError)
|
||||
err.DetailedError = ""
|
||||
conn.Send <- model.NewWebSocketError(r.Seq, err)
|
||||
errResp := model.NewWebSocketError(r.Seq, err)
|
||||
errResp.DoPreComputeJson()
|
||||
|
||||
conn.Send <- errResp
|
||||
return
|
||||
}
|
||||
|
||||
conn.Send <- model.NewWebSocketResponse(model.STATUS_OK, r.Seq, data)
|
||||
resp := model.NewWebSocketResponse(model.STATUS_OK, r.Seq, data)
|
||||
resp.DoPreComputeJson()
|
||||
|
||||
conn.Send <- resp
|
||||
}
|
||||
|
||||
func NewInvalidWebSocketParamError(action string, name string) *model.AppError {
|
||||
|
||||
@@ -54,6 +54,7 @@ func (wr *WebSocketRouter) ReturnWebSocketError(conn *WebConn, r *model.WebSocke
|
||||
|
||||
err.DetailedError = ""
|
||||
errorResp := model.NewWebSocketError(r.Seq, err)
|
||||
errorResp.DoPreComputeJson()
|
||||
|
||||
conn.Send <- errorResp
|
||||
}
|
||||
|
||||
@@ -82,7 +82,8 @@ func TestWebSocketEvent(t *testing.T) {
|
||||
omitUser["somerandomid"] = true
|
||||
evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser)
|
||||
evt1.Add("user_id", "somerandomid")
|
||||
go Publish(evt1)
|
||||
Publish(evt1)
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
stop := make(chan bool)
|
||||
|
||||
Ссылка в новой задаче
Block a user