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
Этот коммит содержится в:
Joram Wilander
2016-10-19 14:49:25 -04:00
коммит произвёл GitHub
родитель 0512bd26ee
Коммит 365b8b465e
129 изменённых файлов: 6411 добавлений и 2530 удалений

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

@@ -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 = ""
}

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

@@ -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"))
}

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

@@ -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")
}
}
*/

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

@@ -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 {

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

@@ -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")
}
}

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

@@ -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()))
}

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

@@ -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")
}
}

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

@@ -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
}
}
}

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

@@ -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)