summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/admin.go10
-rw-r--r--app/app.go18
-rw-r--r--app/apptestlib.go8
-rw-r--r--app/authorization.go12
-rw-r--r--app/auto_constants.go22
-rw-r--r--app/auto_posts.go15
-rw-r--r--app/channel.go45
-rw-r--r--app/channel_test.go69
-rw-r--r--app/command_expand_collapse.go4
-rw-r--r--app/config.go15
-rw-r--r--app/config_test.go10
-rw-r--r--app/emoji.go1
-rw-r--r--app/post.go18
-rw-r--r--app/preference.go8
-rw-r--r--app/server.go47
-rw-r--r--app/session.go3
-rw-r--r--app/slackimport.go12
-rw-r--r--app/status.go16
-rw-r--r--app/team.go99
-rw-r--r--app/user.go10
20 files changed, 260 insertions, 182 deletions
diff --git a/app/admin.go b/app/admin.go
index 154fa8899..22928390e 100644
--- a/app/admin.go
+++ b/app/admin.go
@@ -15,7 +15,6 @@ import (
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/store/sqlstore"
"github.com/mattermost/mattermost-server/utils"
)
@@ -141,10 +140,11 @@ func (a *App) InvalidateAllCachesSkipSend() {
l4g.Info(utils.T("api.context.invalidate_all_caches"))
a.sessionCache.Purge()
ClearStatusCache()
- sqlstore.ClearChannelCaches()
- sqlstore.ClearUserCaches()
- sqlstore.ClearPostCaches()
- sqlstore.ClearWebhookCaches()
+ a.Srv.Store.Channel().ClearCaches()
+ a.Srv.Store.User().ClearCaches()
+ a.Srv.Store.Post().ClearCaches()
+ a.Srv.Store.FileInfo().ClearCaches()
+ a.Srv.Store.Webhook().ClearCaches()
a.LoadLicense()
}
diff --git a/app/app.go b/app/app.go
index 26aed4c73..f5e5dd21e 100644
--- a/app/app.go
+++ b/app/app.go
@@ -131,8 +131,24 @@ func New(options ...Option) (outApp *App, outErr error) {
app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) {
app.configOrLicenseListener()
+
+ message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CONFIG_CHANGED, "", "", "", nil)
+
+ message.Add("config", app.ClientConfigWithNoAccounts())
+ app.Go(func() {
+ app.Publish(message)
+ })
+ })
+ app.licenseListenerId = app.AddLicenseListener(func() {
+ app.configOrLicenseListener()
+
+ message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LICENSE_CHANGED, "", "", "", nil)
+ message.Add("license", app.GetSanitizedClientLicense())
+ app.Go(func() {
+ app.Publish(message)
+ })
+
})
- app.licenseListenerId = app.AddLicenseListener(app.configOrLicenseListener)
app.regenerateClientConfig()
app.setDefaultRolesBasedOnConfig()
diff --git a/app/apptestlib.go b/app/apptestlib.go
index c7846c9b5..9e5bfc637 100644
--- a/app/apptestlib.go
+++ b/app/apptestlib.go
@@ -135,10 +135,6 @@ func (me *TestHelper) InitBasic() *TestHelper {
return me
}
-func (me *TestHelper) MakeUsername() string {
- return "un_" + model.NewId()
-}
-
func (me *TestHelper) MakeEmail() string {
return "success_" + model.NewId() + "@simulator.amazonses.com"
}
@@ -191,10 +187,6 @@ func (me *TestHelper) CreateChannel(team *model.Team) *model.Channel {
return me.createChannel(team, model.CHANNEL_OPEN)
}
-func (me *TestHelper) CreatePrivateChannel(team *model.Team) *model.Channel {
- return me.createChannel(team, model.CHANNEL_PRIVATE)
-}
-
func (me *TestHelper) createChannel(team *model.Team, channelType string) *model.Channel {
id := model.NewId()
diff --git a/app/authorization.go b/app/authorization.go
index 3a64bb717..4231cac77 100644
--- a/app/authorization.go
+++ b/app/authorization.go
@@ -181,18 +181,6 @@ func (a *App) HasPermissionToChannelByPost(askingUserId string, postId string, p
return a.HasPermissionTo(askingUserId, permission)
}
-func (a *App) HasPermissionToUser(askingUserId string, userId string) bool {
- if askingUserId == userId {
- return true
- }
-
- if a.HasPermissionTo(askingUserId, model.PERMISSION_EDIT_OTHER_USERS) {
- return true
- }
-
- return false
-}
-
func (a *App) CheckIfRolesGrantPermission(roles []string, permissionId string) bool {
for _, roleId := range roles {
if role := a.Role(roleId); role == nil {
diff --git a/app/auto_constants.go b/app/auto_constants.go
index c52eb6243..520d4e363 100644
--- a/app/auto_constants.go
+++ b/app/auto_constants.go
@@ -9,16 +9,15 @@ import (
)
const (
- USER_PASSWORD = "passwd"
- CHANNEL_TYPE = model.CHANNEL_OPEN
- FUZZ_USER_EMAIL_PREFIX_LEN = 10
- BTEST_TEAM_DISPLAY_NAME = "TestTeam"
- BTEST_TEAM_NAME = "z-z-testdomaina"
- BTEST_TEAM_EMAIL = "test@nowhere.com"
- BTEST_TEAM_TYPE = model.TEAM_OPEN
- BTEST_USER_NAME = "Mr. Testing Tester"
- BTEST_USER_EMAIL = "success+ttester@simulator.amazonses.com"
- BTEST_USER_PASSWORD = "passwd"
+ USER_PASSWORD = "passwd"
+ CHANNEL_TYPE = model.CHANNEL_OPEN
+ BTEST_TEAM_DISPLAY_NAME = "TestTeam"
+ BTEST_TEAM_NAME = "z-z-testdomaina"
+ BTEST_TEAM_EMAIL = "test@nowhere.com"
+ BTEST_TEAM_TYPE = model.TEAM_OPEN
+ BTEST_USER_NAME = "Mr. Testing Tester"
+ BTEST_USER_EMAIL = "success+ttester@simulator.amazonses.com"
+ BTEST_USER_PASSWORD = "passwd"
)
var (
@@ -29,8 +28,5 @@ var (
USER_EMAIL_LEN = utils.Range{Begin: 15, End: 30}
CHANNEL_DISPLAY_NAME_LEN = utils.Range{Begin: 10, End: 20}
CHANNEL_NAME_LEN = utils.Range{Begin: 5, End: 20}
- POST_MESSAGE_LEN = utils.Range{Begin: 100, End: 400}
- POST_HASHTAGS_NUM = utils.Range{Begin: 5, End: 10}
- POST_MENTIONS_NUM = utils.Range{Begin: 0, End: 3}
TEST_IMAGE_FILENAMES = []string{"test.png", "testjpg.jpg", "testgif.gif"}
)
diff --git a/app/auto_posts.go b/app/auto_posts.go
index 6d1e352e5..379c74ab7 100644
--- a/app/auto_posts.go
+++ b/app/auto_posts.go
@@ -90,18 +90,3 @@ func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) {
}
return result.Data.(*model.Post), true
}
-
-func (cfg *AutoPostCreator) CreateTestPosts(rangePosts utils.Range) ([]*model.Post, bool) {
- numPosts := utils.RandIntFromRange(rangePosts)
- posts := make([]*model.Post, numPosts)
-
- for i := 0; i < numPosts; i++ {
- var err bool
- posts[i], err = cfg.CreateRandomPost()
- if !err {
- return posts, false
- }
- }
-
- return posts, true
-}
diff --git a/app/channel.go b/app/channel.go
index 8ac1f421c..4e294abbb 100644
--- a/app/channel.go
+++ b/app/channel.go
@@ -225,6 +225,14 @@ func (a *App) createDirectChannel(userId string, otherUserId string) (*model.Cha
}
} else {
channel := result.Data.(*model.Channel)
+
+ if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId, channel.Id, model.GetMillis()); result.Err != nil {
+ l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
+ }
+ if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(otherUserId, channel.Id, model.GetMillis()); result.Err != nil {
+ l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
+ }
+
return channel, nil
}
}
@@ -369,7 +377,7 @@ func (a *App) postChannelPrivacyMessage(user *model.User, channel *model.Channel
})[channel.Type]
post := &model.Post{
ChannelId: channel.Id,
- Message: fmt.Sprintf(utils.T("api.channel.change_channel_privacy." + privacy)),
+ Message: utils.T("api.channel.change_channel_privacy." + privacy),
Type: model.POST_CHANGE_CHANNEL_PRIVACY,
UserId: user.Id,
Props: model.StringInterface{
@@ -545,7 +553,6 @@ func (a *App) DeleteChannel(channel *model.Channel, userId string) *model.AppErr
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, channel.TeamId, "", "", nil)
message.Add("channel_id", channel.Id)
-
a.Publish(message)
}
@@ -1055,7 +1062,7 @@ func (a *App) LeaveChannel(channelId string, userId string) *model.AppError {
return err
}
- if channel.Name == model.DEFAULT_CHANNEL && *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages == false {
+ if channel.Name == model.DEFAULT_CHANNEL && !*a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages {
return nil
}
@@ -1093,7 +1100,9 @@ func (a *App) PostAddToChannelMessage(user *model.User, addedUser *model.User, c
UserId: user.Id,
RootId: postRootId,
Props: model.StringInterface{
+ "userId": user.Id,
"username": user.Username,
+ "addedUserId": addedUser.Id,
"addedUsername": addedUser.Username,
},
}
@@ -1113,7 +1122,9 @@ func (a *App) postAddToTeamMessage(user *model.User, addedUser *model.User, chan
UserId: user.Id,
RootId: postRootId,
Props: model.StringInterface{
+ "userId": user.Id,
"username": user.Username,
+ "addedUserId": addedUser.Id,
"addedUsername": addedUser.Username,
},
}
@@ -1132,6 +1143,7 @@ func (a *App) postRemoveFromChannelMessage(removerUserId string, removedUser *mo
Type: model.POST_REMOVE_FROM_CHANNEL,
UserId: removerUserId,
Props: model.StringInterface{
+ "removedUserId": removedUser.Id,
"removedUsername": removedUser.Username,
},
}
@@ -1166,17 +1178,13 @@ func (a *App) removeUserFromChannel(userIdToRemove string, removerUserId string,
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil)
message.Add("user_id", userIdToRemove)
message.Add("remover_id", removerUserId)
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
// because the removed user no longer belongs to the channel we need to send a separate websocket event
userMsg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", "", userIdToRemove, nil)
userMsg.Add("channel_id", channel.Id)
userMsg.Add("remover_id", removerUserId)
- a.Go(func() {
- a.Publish(userMsg)
- })
+ a.Publish(userMsg)
return nil
}
@@ -1246,9 +1254,7 @@ func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *mod
for _, channelId := range channelIds {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil)
message.Add("channel_id", channelId)
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
}
}
@@ -1325,9 +1331,7 @@ func (a *App) ViewChannel(view *model.ChannelView, userId string, clearPushNotif
if *a.Config().ServiceSettings.EnableChannelViewedMessages && model.IsValidId(view.ChannelId) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", userId, nil)
message.Add("channel_id", view.ChannelId)
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
}
return times, nil
@@ -1430,7 +1434,16 @@ func (a *App) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.
}
a.InvalidateCacheForUser(userId1)
a.InvalidateCacheForUser(userId2)
- return result.Data.(*model.Channel), nil
+
+ channel := result.Data.(*model.Channel)
+ if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId1, channel.Id, model.GetMillis()); result.Err != nil {
+ l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
+ }
+ if result := <-a.Srv.Store.ChannelMemberHistory().LogJoinEvent(userId2, channel.Id, model.GetMillis()); result.Err != nil {
+ l4g.Warn("Failed to update ChannelMemberHistory table %v", result.Err)
+ }
+
+ return channel, nil
} else if result.Err != nil {
return nil, model.NewAppError("GetOrCreateDMChannel", "web.incoming_webhook.channel.app_error", nil, "err="+result.Err.Message, result.Err.StatusCode)
}
diff --git a/app/channel_test.go b/app/channel_test.go
index e4a0e4320..69efaeca7 100644
--- a/app/channel_test.go
+++ b/app/channel_test.go
@@ -110,7 +110,7 @@ func TestMoveChannel(t *testing.T) {
}
}
-func TestJoinDefaultChannelsTownSquare(t *testing.T) {
+func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordTownSquare(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -136,7 +136,7 @@ func TestJoinDefaultChannelsTownSquare(t *testing.T) {
assert.True(t, found)
}
-func TestJoinDefaultChannelsOffTopic(t *testing.T) {
+func TestJoinDefaultChannelsCreatesChannelMemberHistoryRecordOffTopic(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -162,7 +162,7 @@ func TestJoinDefaultChannelsOffTopic(t *testing.T) {
assert.True(t, found)
}
-func TestCreateChannelPublic(t *testing.T) {
+func TestCreateChannelPublicCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -176,7 +176,7 @@ func TestCreateChannelPublic(t *testing.T) {
assert.Equal(t, publicChannel.Id, histories[0].ChannelId)
}
-func TestCreateChannelPrivate(t *testing.T) {
+func TestCreateChannelPrivateCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -205,7 +205,7 @@ func TestUpdateChannelPrivacy(t *testing.T) {
}
}
-func TestCreateGroupChannel(t *testing.T) {
+func TestCreateGroupChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -233,7 +233,62 @@ func TestCreateGroupChannel(t *testing.T) {
}
}
-func TestAddUserToChannel(t *testing.T) {
+func TestCreateDirectChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
+ th := Setup().InitBasic()
+ defer th.TearDown()
+
+ user1 := th.CreateUser()
+ user2 := th.CreateUser()
+
+ if channel, err := th.App.CreateDirectChannel(user1.Id, user2.Id); err != nil {
+ t.Fatal("Failed to create direct channel. Error: " + err.Message)
+ } else {
+ // there should be a ChannelMemberHistory record for both users
+ histories := store.Must(th.App.Srv.Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, channel.Id)).([]*model.ChannelMemberHistoryResult)
+ assert.Len(t, histories, 2)
+
+ historyId0 := histories[0].UserId
+ historyId1 := histories[1].UserId
+ switch historyId0 {
+ case user1.Id:
+ assert.Equal(t, user2.Id, historyId1)
+ case user2.Id:
+ assert.Equal(t, user1.Id, historyId1)
+ default:
+ t.Fatal("Unexpected user id " + historyId0 + " in ChannelMemberHistory table")
+ }
+ }
+}
+
+func TestGetDirectChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
+ th := Setup().InitBasic()
+ defer th.TearDown()
+
+ user1 := th.CreateUser()
+ user2 := th.CreateUser()
+
+ // this function call implicitly creates a direct channel between the two users if one doesn't already exist
+ if channel, err := th.App.GetDirectChannel(user1.Id, user2.Id); err != nil {
+ t.Fatal("Failed to create direct channel. Error: " + err.Message)
+ } else {
+ // there should be a ChannelMemberHistory record for both users
+ histories := store.Must(th.App.Srv.Store.ChannelMemberHistory().GetUsersInChannelDuring(model.GetMillis()-100, model.GetMillis()+100, channel.Id)).([]*model.ChannelMemberHistoryResult)
+ assert.Len(t, histories, 2)
+
+ historyId0 := histories[0].UserId
+ historyId1 := histories[1].UserId
+ switch historyId0 {
+ case user1.Id:
+ assert.Equal(t, user2.Id, historyId1)
+ case user2.Id:
+ assert.Equal(t, user1.Id, historyId1)
+ default:
+ t.Fatal("Unexpected user id " + historyId0 + " in ChannelMemberHistory table")
+ }
+ }
+}
+
+func TestAddUserToChannelCreatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
@@ -263,7 +318,7 @@ func TestAddUserToChannel(t *testing.T) {
assert.Equal(t, groupUserIds, channelMemberHistoryUserIds)
}
-func TestRemoveUserFromChannel(t *testing.T) {
+func TestRemoveUserFromChannelUpdatesChannelMemberHistoryRecord(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
diff --git a/app/command_expand_collapse.go b/app/command_expand_collapse.go
index a8eb3bc1f..638490c6c 100644
--- a/app/command_expand_collapse.go
+++ b/app/command_expand_collapse.go
@@ -74,9 +74,7 @@ func (a *App) setCollapsePreference(args *model.CommandArgs, isCollapse bool) *m
socketMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCE_CHANGED, "", "", args.UserId, nil)
socketMessage.Add("preference", pref.ToJson())
- a.Go(func() {
- a.Publish(socketMessage)
- })
+ a.Publish(socketMessage)
var rmsg string
diff --git a/app/config.go b/app/config.go
index 35a0c9a3f..460d580d8 100644
--- a/app/config.go
+++ b/app/config.go
@@ -14,6 +14,7 @@ import (
"fmt"
"net/url"
"runtime/debug"
+ "strconv"
"strings"
l4g "github.com/alecthomas/log4go"
@@ -34,6 +35,7 @@ func (a *App) UpdateConfig(f func(*model.Config)) {
updated := old.Clone()
f(updated)
a.config.Store(updated)
+
a.InvokeConfigListeners(old, updated)
}
@@ -269,3 +271,16 @@ func (a *App) GetCookieDomain() string {
func (a *App) GetSiteURL() string {
return a.siteURL
}
+
+// ClientConfigWithNoAccounts gets the configuration in a format suitable for sending to the client.
+func (a *App) ClientConfigWithNoAccounts() map[string]string {
+ respCfg := map[string]string{}
+ for k, v := range a.ClientConfig() {
+ respCfg[k] = v
+ }
+
+ // NoAccounts is not actually part of the configuration, but is expected by the client.
+ respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
+
+ return respCfg
+}
diff --git a/app/config_test.go b/app/config_test.go
index 5ee999f0f..051fa8fd8 100644
--- a/app/config_test.go
+++ b/app/config_test.go
@@ -63,3 +63,13 @@ func TestAsymmetricSigningKey(t *testing.T) {
assert.NotNil(t, th.App.AsymmetricSigningKey())
assert.NotEmpty(t, th.App.ClientConfig()["AsymmetricSigningPublicKey"])
}
+
+func TestClientConfigWithNoAccounts(t *testing.T) {
+ th := Setup().InitBasic()
+ defer th.TearDown()
+
+ config := th.App.ClientConfigWithNoAccounts()
+ if _, ok := config["NoAccounts"]; !ok {
+ t.Fatal("expected NoAccounts in returned config")
+ }
+}
diff --git a/app/emoji.go b/app/emoji.go
index 20d4bb44d..eebe59ccf 100644
--- a/app/emoji.go
+++ b/app/emoji.go
@@ -60,7 +60,6 @@ func (a *App) CreateEmoji(sessionUserId string, emoji *model.Emoji, multiPartIma
} else {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EMOJI_ADDED, "", "", "", nil)
message.Add("emoji", emoji.ToJson())
-
a.Publish(message)
return result.Data.(*model.Emoji), nil
}
diff --git a/app/post.go b/app/post.go
index a541797fa..5067777ab 100644
--- a/app/post.go
+++ b/app/post.go
@@ -84,9 +84,7 @@ func (a *App) CreatePostAsUser(post *model.Post) (*model.Post, *model.AppError)
if *a.Config().ServiceSettings.EnableChannelViewedMessages {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", post.UserId, nil)
message.Add("channel_id", post.ChannelId)
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
}
}
@@ -314,10 +312,7 @@ func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil)
message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
-
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
return post
}
@@ -424,10 +419,7 @@ func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *mo
func (a *App) sendUpdatedPostEvent(post *model.Post) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil)
message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
-
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
}
func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) {
@@ -567,11 +559,9 @@ func (a *App) DeletePost(postId string) (*model.Post, *model.AppError) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil)
message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson())
+ a.Publish(message)
a.Go(func() {
- a.Publish(message)
- })
- a.Go(func() {
a.DeletePostFiles(post)
})
a.Go(func() {
diff --git a/app/preference.go b/app/preference.go
index 9ca1f474c..eb41992da 100644
--- a/app/preference.go
+++ b/app/preference.go
@@ -55,9 +55,7 @@ func (a *App) UpdatePreferences(userId string, preferences model.Preferences) *m
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCES_CHANGED, "", "", userId, nil)
message.Add("preferences", preferences.ToJson())
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
return nil
}
@@ -80,9 +78,7 @@ func (a *App) DeletePreferences(userId string, preferences model.Preferences) *m
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCES_DELETED, "", "", userId, nil)
message.Add("preferences", preferences.ToJson())
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
return nil
}
diff --git a/app/server.go b/app/server.go
index 93804a372..0c6c25ba5 100644
--- a/app/server.go
+++ b/app/server.go
@@ -84,28 +84,6 @@ func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
-type VaryBy struct {
- useIP bool
- useAuth bool
-}
-
-func (m *VaryBy) Key(r *http.Request) string {
- key := ""
-
- if m.useAuth {
- token, tokenLocation := ParseAuthTokenFromRequest(r)
- if tokenLocation != TokenLocationNotFound {
- key += token
- } else if m.useIP { // If we don't find an authentication token and IP based is enabled, fall back to IP
- key += utils.GetIpAddress(r)
- }
- } else if m.useIP { // Only if Auth based is not enabed do we use a plain IP based
- key = utils.GetIpAddress(r)
- }
-
- return key
-}
-
func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) {
if r.Host == "" {
http.Error(w, "Not Found", http.StatusNotFound)
@@ -223,31 +201,6 @@ func (a *App) StartServer() error {
return nil
}
-type tcpKeepAliveListener struct {
- *net.TCPListener
-}
-
-func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
- tc, err := ln.AcceptTCP()
- if err != nil {
- return
- }
- tc.SetKeepAlive(true)
- tc.SetKeepAlivePeriod(3 * time.Minute)
- return tc, nil
-}
-
-func (a *App) Listen(addr string) (net.Listener, error) {
- if addr == "" {
- addr = ":http"
- }
- ln, err := net.Listen("tcp", addr)
- if err != nil {
- return nil, err
- }
- return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil
-}
-
func (a *App) StopServer() {
if a.Srv.Server != nil {
ctx, cancel := context.WithTimeout(context.Background(), TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN)
diff --git a/app/session.go b/app/session.go
index 459618439..88f52477f 100644
--- a/app/session.go
+++ b/app/session.go
@@ -138,6 +138,9 @@ func (a *App) ClearSessionCacheForUserSkipClusterSend(userId string) {
session := ts.(*model.Session)
if session.UserId == userId {
a.sessionCache.Remove(key)
+ if a.Metrics != nil {
+ a.Metrics.IncrementMemCacheInvalidationCounterSession()
+ }
}
}
}
diff --git a/app/slackimport.go b/app/slackimport.go
index 9d1b4cf9c..ed522671a 100644
--- a/app/slackimport.go
+++ b/app/slackimport.go
@@ -109,13 +109,11 @@ func SlackParseUsers(data io.Reader) ([]SlackUser, error) {
decoder := json.NewDecoder(data)
var users []SlackUser
- if err := decoder.Decode(&users); err != nil {
- // This actually returns errors that are ignored.
- // In this case it is erroring because of a null that Slack
- // introduced. So we just return the users here.
- return users, err
- }
- return users, nil
+ err := decoder.Decode(&users)
+ // This actually returns errors that are ignored.
+ // In this case it is erroring because of a null that Slack
+ // introduced. So we just return the users here.
+ return users, err
}
func SlackParsePosts(data io.Reader) ([]SlackPost, error) {
diff --git a/app/status.go b/app/status.go
index 1ef7aef0f..d677f9a23 100644
--- a/app/status.go
+++ b/app/status.go
@@ -221,9 +221,7 @@ func (a *App) BroadcastStatus(status *model.Status) {
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", status.Status)
event.Add("user_id", status.UserId)
- a.Go(func() {
- a.Publish(event)
- })
+ a.Publish(event)
}
func (a *App) SetStatusOffline(userId string, manual bool) {
@@ -247,9 +245,7 @@ func (a *App) SetStatusOffline(userId string, manual bool) {
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", model.STATUS_OFFLINE)
event.Add("user_id", status.UserId)
- a.Go(func() {
- a.Publish(event)
- })
+ a.Publish(event)
}
func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
@@ -290,9 +286,7 @@ func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", model.STATUS_AWAY)
event.Add("user_id", status.UserId)
- a.Go(func() {
- a.Publish(event)
- })
+ a.Publish(event)
}
func (a *App) SetStatusDoNotDisturb(userId string) {
@@ -318,9 +312,7 @@ func (a *App) SetStatusDoNotDisturb(userId string) {
event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
event.Add("status", model.STATUS_DND)
event.Add("user_id", status.UserId)
- a.Go(func() {
- a.Publish(event)
- })
+ a.Publish(event)
}
func GetStatusFromCache(userId string) *model.Status {
diff --git a/app/team.go b/app/team.go
index d8750bfbb..239ce4369 100644
--- a/app/team.go
+++ b/app/team.go
@@ -4,13 +4,18 @@
package app
import (
+ "bytes"
"fmt"
+ "image"
+ "image/png"
+ "mime/multipart"
"net/http"
"net/url"
"strconv"
"strings"
l4g "github.com/alecthomas/log4go"
+ "github.com/disintegration/imaging"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
@@ -134,9 +139,7 @@ func (a *App) sendTeamEvent(team *model.Team, event string) {
message := model.NewWebSocketEvent(event, "", "", "", nil)
message.Add("team", sanitizedTeam.ToJson())
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
}
func (a *App) UpdateTeamMemberRoles(teamId string, userId string, newRoles string) (*model.TeamMember, *model.AppError) {
@@ -173,10 +176,7 @@ func (a *App) UpdateTeamMemberRoles(teamId string, userId string, newRoles strin
func (a *App) sendUpdatedMemberRoleEvent(userId string, member *model.TeamMember) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_MEMBERROLE_UPDATED, "", "", userId, nil)
message.Add("member", member.ToJson())
-
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
}
func (a *App) AddUserToTeam(teamId string, userId string, userRequestorId string) (*model.Team, *model.AppError) {
@@ -919,3 +919,88 @@ func (a *App) SanitizeTeams(session model.Session, teams []*model.Team) []*model
return teams
}
+
+func (a *App) GetTeamIcon(team *model.Team) ([]byte, *model.AppError) {
+ if len(*a.Config().FileSettings.DriverName) == 0 {
+ return nil, model.NewAppError("GetTeamIcon", "api.team.get_team_icon.filesettings_no_driver.app_error", nil, "", http.StatusNotImplemented)
+ } else {
+ path := "teams/" + team.Id + "/teamIcon.png"
+ if data, err := a.ReadFile(path); err != nil {
+ return nil, model.NewAppError("GetTeamIcon", "api.team.get_team_icon.read_file.app_error", nil, err.Error(), http.StatusNotFound)
+ } else {
+ return data, nil
+ }
+ }
+}
+
+func (a *App) SetTeamIcon(teamId string, imageData *multipart.FileHeader) *model.AppError {
+ file, err := imageData.Open()
+ if err != nil {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.open.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+ defer file.Close()
+ return a.SetTeamIconFromFile(teamId, file)
+}
+
+func (a *App) SetTeamIconFromFile(teamId string, file multipart.File) *model.AppError {
+
+ team, getTeamErr := a.GetTeam(teamId)
+
+ if getTeamErr != nil {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.get_team.app_error", nil, getTeamErr.Error(), http.StatusBadRequest)
+ }
+
+ if len(*a.Config().FileSettings.DriverName) == 0 {
+ return model.NewAppError("setTeamIcon", "api.team.set_team_icon.storage.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ // Decode image config first to check dimensions before loading the whole thing into memory later on
+ config, _, err := image.DecodeConfig(file)
+ if err != nil {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.decode_config.app_error", nil, err.Error(), http.StatusBadRequest)
+ } else if config.Width*config.Height > model.MaxImageSize {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.too_large.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ file.Seek(0, 0)
+
+ // Decode image into Image object
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.decode.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ file.Seek(0, 0)
+
+ orientation, _ := getImageOrientation(file)
+ img = makeImageUpright(img, orientation)
+
+ // Scale team icon
+ teamIconWidthAndHeight := 128
+ img = imaging.Fill(img, teamIconWidthAndHeight, teamIconWidthAndHeight, imaging.Center, imaging.Lanczos)
+
+ buf := new(bytes.Buffer)
+ err = png.Encode(buf, img)
+ if err != nil {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.encode.app_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ path := "teams/" + teamId + "/teamIcon.png"
+
+ if err := a.WriteFile(buf.Bytes(), path); err != nil {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.write_file.app_error", nil, "", http.StatusInternalServerError)
+ }
+
+ curTime := model.GetMillis()
+
+ if result := <-a.Srv.Store.Team().UpdateLastTeamIconUpdate(teamId, curTime); result.Err != nil {
+ return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.update.app_error", nil, result.Err.Error(), http.StatusBadRequest)
+ }
+
+ // manually set time to avoid possible cluster inconsistencies
+ team.LastTeamIconUpdate = curTime
+
+ a.sendTeamEvent(team, model.WEBSOCKET_EVENT_UPDATE_TEAM)
+
+ return nil
+}
diff --git a/app/user.go b/app/user.go
index f915f35cb..c303cbc68 100644
--- a/app/user.go
+++ b/app/user.go
@@ -34,7 +34,6 @@ const (
TOKEN_TYPE_PASSWORD_RECOVERY = "password_recovery"
TOKEN_TYPE_VERIFY_EMAIL = "verify_email"
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
- VERIFY_EMAIL_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
IMAGE_PROFILE_PIXEL_DIMENSION = 128
)
@@ -202,9 +201,7 @@ func (a *App) CreateUser(user *model.User) (*model.User, *model.AppError) {
// This message goes to everyone, so the teamId, channelId and userId are irrelevant
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_NEW_USER, "", "", "", nil)
message.Add("user_id", ruser.Id)
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
return ruser, nil
}
@@ -832,7 +829,6 @@ func (a *App) SetProfileImageFromFile(userId string, file multipart.File) *model
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", user)
-
a.Publish(message)
}
@@ -1002,9 +998,7 @@ func (a *App) sendUpdatedUserEvent(user model.User, asAdmin bool) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
message.Add("user", user)
- a.Go(func() {
- a.Publish(message)
- })
+ a.Publish(message)
}
func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User, *model.AppError) {