diff options
author | Christopher Speller <crspeller@gmail.com> | 2017-01-12 19:59:37 -0500 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2017-01-12 19:59:37 -0500 |
commit | 8fb66c2b834ee4e0669de173e5ddfebd56c34d58 (patch) | |
tree | 9e8e66772d44c4f9010a48c8e0afed70421238b1 /api | |
parent | 4208e42ba9df02b9c4bd7081179922c21206d83d (diff) | |
parent | aafb8be87c79c60df7534b3b69f967c6301b157e (diff) | |
download | chat-8fb66c2b834ee4e0669de173e5ddfebd56c34d58.tar.gz chat-8fb66c2b834ee4e0669de173e5ddfebd56c34d58.tar.bz2 chat-8fb66c2b834ee4e0669de173e5ddfebd56c34d58.zip |
Merge branch 'release-3.6'
Diffstat (limited to 'api')
-rw-r--r-- | api/channel.go | 47 | ||||
-rw-r--r-- | api/command.go | 19 | ||||
-rw-r--r-- | api/command_invite_people.go | 2 | ||||
-rw-r--r-- | api/post.go | 488 | ||||
-rw-r--r-- | api/status.go | 52 | ||||
-rw-r--r-- | api/team.go | 12 |
6 files changed, 319 insertions, 301 deletions
diff --git a/api/channel.go b/api/channel.go index 4c9e514ac..ae92ab618 100644 --- a/api/channel.go +++ b/api/channel.go @@ -826,47 +826,42 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } + post := &model.Post{ + ChannelId: channel.Id, + Message: fmt.Sprintf(c.T("api.channel.delete_channel.archived"), user.Username), + Type: model.POST_CHANNEL_DELETED, + UserId: c.Session.UserId, + } + + if _, err := CreatePost(c, post, false); err != nil { + l4g.Error(utils.T("api.channel.delete_channel.failed_post.error"), err) + } + now := model.GetMillis() for _, hook := range incomingHooks { - go func() { - if result := <-Srv.Store.Webhook().DeleteIncoming(hook.Id, now); result.Err != nil { - l4g.Error(utils.T("api.channel.delete_channel.incoming_webhook.error"), hook.Id) - } - }() + if result := <-Srv.Store.Webhook().DeleteIncoming(hook.Id, now); result.Err != nil { + l4g.Error(utils.T("api.channel.delete_channel.incoming_webhook.error"), hook.Id) + } } for _, hook := range outgoingHooks { - go func() { - if result := <-Srv.Store.Webhook().DeleteOutgoing(hook.Id, now); result.Err != nil { - l4g.Error(utils.T("api.channel.delete_channel.outgoing_webhook.error"), hook.Id) - } - }() + if result := <-Srv.Store.Webhook().DeleteOutgoing(hook.Id, now); result.Err != nil { + l4g.Error(utils.T("api.channel.delete_channel.outgoing_webhook.error"), hook.Id) + } } - InvalidateCacheForChannel(channel.Id) if dresult := <-Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); dresult.Err != nil { c.Err = dresult.Err return } + InvalidateCacheForChannel(channel.Id) c.LogAudit("name=" + channel.Name) - go func() { - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, c.TeamId, "", "", nil) - message.Add("channel_id", 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{ - ChannelId: channel.Id, - Message: fmt.Sprintf(c.T("api.channel.delete_channel.archived"), user.Username), - Type: model.POST_CHANNEL_DELETED, - UserId: c.Session.UserId, - } - if _, err := CreatePost(c, post, false); err != nil { - l4g.Error(utils.T("api.channel.delete_channel.failed_post.error"), err) - } - }() + Publish(message) result := make(map[string]string) result["id"] = channel.Id diff --git a/api/command.go b/api/command.go index 842d67843..9c8f60be5 100644 --- a/api/command.go +++ b/api/command.go @@ -134,7 +134,6 @@ func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { return } else { team = tr.Data.(*model.Team) - } var user *model.User @@ -247,23 +246,7 @@ func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandRe } } - if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL { - post.Message = response.Text - post.UserId = c.Session.UserId - if _, err := CreatePost(c, post, true); err != nil { - c.Err = model.NewLocAppError("command", "api.command.execute_command.save.app_error", nil, "") - } - } else if response.ResponseType == model.COMMAND_RESPONSE_TYPE_EPHEMERAL && response.Text != "" { - post.Message = response.Text - post.CreateAt = model.GetMillis() - post.UserId = c.Session.UserId - post.ParentId = "" - SendEphemeralPost( - c.TeamId, - c.Session.UserId, - post, - ) - } + CreateCommandPost(c, post, response) w.Write([]byte(response.ToJson())) } diff --git a/api/command_invite_people.go b/api/command_invite_people.go index ca2a2633a..e6ad252f6 100644 --- a/api/command_invite_people.go +++ b/api/command_invite_people.go @@ -72,7 +72,7 @@ func (me *InvitePeopleProvider) DoCommand(c *Context, args *model.CommandArgs, m user = result.Data.(*model.User) } - go InviteMembers(team, user.GetDisplayName(), emailList) + go InviteMembers(team, user.GetDisplayName(), emailList, c.GetSiteURL()) return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.sent")} } diff --git a/api/post.go b/api/post.go index e8aef2f86..8d255649e 100644 --- a/api/post.go +++ b/api/post.go @@ -174,11 +174,9 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post return rpost, nil } -func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string) (*model.Post, *model.AppError) { - // parse links into Markdown format - linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) - text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") +var linkWithTextRegex *regexp.Regexp = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) +func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string) (*model.Post, *model.AppError) { post := &model.Post{UserId: c.Session.UserId, ChannelId: channelId, Message: text, Type: postType} post.AddProp("from_webhook", "true") @@ -196,41 +194,12 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc } } + post.Message = parseSlackLinksToMarkdown(post.Message) + if len(props) > 0 { for key, val := range props { if key == "attachments" { - if list, success := val.([]interface{}); success { - // parse attachment links into Markdown format - for i, aInt := range list { - attachment := aInt.(map[string]interface{}) - if aText, ok := attachment["text"].(string); ok { - aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})") - attachment["text"] = aText - list[i] = attachment - } - if aText, ok := attachment["pretext"].(string); ok { - aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})") - attachment["pretext"] = aText - list[i] = attachment - } - if fVal, ok := attachment["fields"]; ok { - if fields, ok := fVal.([]interface{}); ok { - // parse attachment field links into Markdown format - for j, fInt := range fields { - field := fInt.(map[string]interface{}) - if fValue, ok := field["value"].(string); ok { - fValue = linkWithTextRegex.ReplaceAllString(fValue, "[${2}](${1})") - field["value"] = fValue - fields[j] = field - } - } - attachment["fields"] = fields - list[i] = attachment - } - } - } - post.AddProp(key, list) - } + parseSlackAttachment(post, val) } else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" { post.AddProp(key, val) } @@ -244,6 +213,72 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc return post, nil } +func CreateCommandPost(c *Context, post *model.Post, response *model.CommandResponse) { + post.Message = parseSlackLinksToMarkdown(response.Text) + post.UserId = c.Session.UserId + post.CreateAt = model.GetMillis() + + if response.Attachments != nil { + parseSlackAttachment(post, response.Attachments) + } + + switch response.ResponseType { + case model.COMMAND_RESPONSE_TYPE_IN_CHANNEL: + if _, err := CreatePost(c, post, true); err != nil { + c.Err = model.NewLocAppError("command", "api.command.execute_command.save.app_error", nil, "") + } + case model.COMMAND_RESPONSE_TYPE_EPHEMERAL: + if response.Text == "" { + return + } + + post.ParentId = "" + SendEphemeralPost(c.TeamId, c.Session.UserId, post) + } +} + +// This method only parses and processes the attachments, +// all else should be set in the post which is passed +func parseSlackAttachment(post *model.Post, attachments interface{}) { + post.Type = model.POST_SLACK_ATTACHMENT + + if list, success := attachments.([]interface{}); success { + for i, aInt := range list { + attachment := aInt.(map[string]interface{}) + if aText, ok := attachment["text"].(string); ok { + aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})") + attachment["text"] = aText + list[i] = attachment + } + if aText, ok := attachment["pretext"].(string); ok { + aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})") + attachment["pretext"] = aText + list[i] = attachment + } + if fVal, ok := attachment["fields"]; ok { + if fields, ok := fVal.([]interface{}); ok { + // parse attachment field links into Markdown format + for j, fInt := range fields { + field := fInt.(map[string]interface{}) + if fValue, ok := field["value"].(string); ok { + fValue = linkWithTextRegex.ReplaceAllString(fValue, "[${2}](${1})") + field["value"] = fValue + fields[j] = field + } + } + attachment["fields"] = fields + list[i] = attachment + } + } + } + post.AddProp("attachments", list) + } +} + +func parseSlackLinksToMarkdown(text string) string { + return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") +} + func handlePostEvents(c *Context, post *model.Post, triggerWebhooks bool) { tchan := Srv.Store.Team().Get(c.TeamId) cchan := Srv.Store.Channel().Get(post.ChannelId, true) @@ -573,190 +608,190 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri } func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel) []string { - pchan := Srv.Store.User().GetProfilesInChannel(channel.Id, -1, -1, true) - fchan := Srv.Store.FileInfo().GetForPost(post.Id) + mentionedUsersList := make([]string, 0) + var fchan store.StoreChannel + var senderUsername string - var profileMap map[string]*model.User - if result := <-pchan; result.Err != nil { - l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err) - return nil + if post.IsSystemMessage() { + senderUsername = c.T("system.message.name") } else { - profileMap = result.Data.(map[string]*model.User) - } - - // 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 nil - } + pchan := Srv.Store.User().GetProfilesInChannel(channel.Id, -1, -1, true) + fchan = Srv.Store.FileInfo().GetForPost(post.Id) - mentionedUserIds := make(map[string]bool) - allActivityPushUserIds := []string{} - hereNotification := false - channelNotification := false - allNotification := false - updateMentionChans := []store.StoreChannel{} - - if channel.Type == model.CHANNEL_DIRECT { - var otherUserId string - if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId { - otherUserId = userIds[1] + var profileMap map[string]*model.User + if result := <-pchan; result.Err != nil { + l4g.Error(utils.T("api.post.handle_post_events_and_forget.profiles.error"), c.TeamId, result.Err) + return nil } else { - otherUserId = userIds[0] + profileMap = result.Data.(map[string]*model.User) } - mentionedUserIds[otherUserId] = true - if post.Props["from_webhook"] == "true" { - mentionedUserIds[post.UserId] = true + // 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 nil } - } else { - keywords := getMentionKeywordsInChannel(profileMap) - var potentialOtherMentions []string - mentionedUserIds, potentialOtherMentions, hereNotification, channelNotification, allNotification = getExplicitMentions(post.Message, keywords) + mentionedUserIds := make(map[string]bool) + allActivityPushUserIds := []string{} + hereNotification := false + channelNotification := false + allNotification := false + updateMentionChans := []store.StoreChannel{} - // get users that have comment thread mentions enabled - if len(post.RootId) > 0 { - if result := <-Srv.Store.Post().Get(post.RootId); result.Err != nil { - l4g.Error(utils.T("api.post.send_notifications_and_forget.comment_thread.error"), post.RootId, result.Err) - return nil + if channel.Type == model.CHANNEL_DIRECT { + var otherUserId string + if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId { + otherUserId = userIds[1] } else { - list := result.Data.(*model.PostList) + otherUserId = userIds[0] + } + + mentionedUserIds[otherUserId] = true + if post.Props["from_webhook"] == "true" { + mentionedUserIds[post.UserId] = true + } + } else { + keywords := getMentionKeywordsInChannel(profileMap) + + var potentialOtherMentions []string + mentionedUserIds, potentialOtherMentions, hereNotification, channelNotification, allNotification = getExplicitMentions(post.Message, keywords) + + // get users that have comment thread mentions enabled + if len(post.RootId) > 0 { + if result := <-Srv.Store.Post().Get(post.RootId); result.Err != nil { + l4g.Error(utils.T("api.post.send_notifications_and_forget.comment_thread.error"), post.RootId, result.Err) + return nil + } else { + list := result.Data.(*model.PostList) - for _, threadPost := range list.Posts { - profile := profileMap[threadPost.UserId] - if profile.NotifyProps["comments"] == "any" || (profile.NotifyProps["comments"] == "root" && threadPost.Id == list.Order[0]) { - mentionedUserIds[threadPost.UserId] = true + for _, threadPost := range list.Posts { + if profile, ok := profileMap[threadPost.UserId]; ok { + if profile.NotifyProps["comments"] == "any" || (profile.NotifyProps["comments"] == "root" && threadPost.Id == list.Order[0]) { + mentionedUserIds[threadPost.UserId] = true + } + } } } } - } - // prevent the user from mentioning themselves - if post.Props["from_webhook"] != "true" { - delete(mentionedUserIds, post.UserId) - } + // prevent the user from mentioning themselves + if post.Props["from_webhook"] != "true" { + delete(mentionedUserIds, post.UserId) + } - 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) + 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) + } } - } - // find which users in the channel are set up to always receive mobile notifications - for _, profile := range profileMap { - if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL && - (post.UserId != profile.Id || post.Props["from_webhook"] == "true") && - !post.IsSystemMessage() { - allActivityPushUserIds = append(allActivityPushUserIds, profile.Id) + // find which users in the channel are set up to always receive mobile notifications + for _, profile := range profileMap { + if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL && + (post.UserId != profile.Id || post.Props["from_webhook"] == "true") { + allActivityPushUserIds = append(allActivityPushUserIds, profile.Id) + } } } - } - mentionedUsersList := make([]string, 0, len(mentionedUserIds)) - for id := range mentionedUserIds { - mentionedUsersList = append(mentionedUsersList, id) - updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, id)) - } + mentionedUsersList = make([]string, 0, len(mentionedUserIds)) + for id := range mentionedUserIds { + mentionedUsersList = append(mentionedUsersList, id) + updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, id)) + } - var sender *model.User - senderName := make(map[string]string) - for _, id := range mentionedUsersList { - senderName[id] = "" - if post.IsSystemMessage() { - senderName[id] = c.T("system.message.name") - } else if profile, ok := profileMap[post.UserId]; ok { - if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" { - senderName[id] = value.(string) - } else { - //Get the Display name preference from the receiver - if result := <-Srv.Store.Preference().Get(id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "name_format"); result.Err != nil { - // Show default sender's name if user doesn't set display settings. - senderName[id] = profile.Username + var sender *model.User + senderName := make(map[string]string) + for _, id := range mentionedUsersList { + senderName[id] = "" + if profile, ok := profileMap[post.UserId]; ok { + if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" { + senderName[id] = value.(string) } else { - senderName[id] = profile.GetDisplayNameForPreference(result.Data.(model.Preference).Value) + //Get the Display name preference from the receiver + if result := <-Srv.Store.Preference().Get(id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "name_format"); result.Err != nil { + // Show default sender's name if user doesn't set display settings. + senderName[id] = profile.Username + } else { + senderName[id] = profile.GetDisplayNameForPreference(result.Data.(model.Preference).Value) + } } + sender = profile } - sender = profile } - } - var senderUsername string - if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" { - senderUsername = value.(string) - } else { - senderUsername = profileMap[post.UserId].Username - } + if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" { + senderUsername = value.(string) + } else { + senderUsername = profileMap[post.UserId].Username + } - if utils.Cfg.EmailSettings.SendEmailNotifications { - for _, id := range mentionedUsersList { - userAllowsEmails := profileMap[id].NotifyProps["email"] != "false" - - var status *model.Status - var err *model.AppError - if status, err = GetStatus(id); err != nil { - status = &model.Status{ - UserId: id, - Status: model.STATUS_OFFLINE, - Manual: false, - LastActivityAt: 0, - ActiveChannel: "", + if utils.Cfg.EmailSettings.SendEmailNotifications { + for _, id := range mentionedUsersList { + userAllowsEmails := profileMap[id].NotifyProps["email"] != "false" + + var status *model.Status + var err *model.AppError + if status, err = GetStatus(id); err != nil { + status = &model.Status{ + UserId: id, + Status: model.STATUS_OFFLINE, + Manual: false, + LastActivityAt: 0, + ActiveChannel: "", + } } - } - if userAllowsEmails && status.Status != model.STATUS_ONLINE { - sendNotificationEmail(c, post, profileMap[id], channel, team, senderName[id], sender) + if userAllowsEmails && status.Status != model.STATUS_ONLINE { + sendNotificationEmail(c, post, profileMap[id], channel, team, senderName[id], sender) + } } } - } - // If the channel has more than 1K users then @here is disabled - if hereNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { - hereNotification = false - SendEphemeralPost( - c.TeamId, - post.UserId, - &model.Post{ - ChannelId: post.ChannelId, - Message: c.T("api.post.disabled_here", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}), - CreateAt: post.CreateAt + 1, - }, - ) - } - - // If the channel has more than 1K users then @channel is disabled - if channelNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { - SendEphemeralPost( - c.TeamId, - post.UserId, - &model.Post{ - ChannelId: post.ChannelId, - Message: c.T("api.post.disabled_channel", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}), - CreateAt: post.CreateAt + 1, - }, - ) - } - - // If the channel has more than 1K users then @all is disabled - if allNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { - SendEphemeralPost( - c.TeamId, - post.UserId, - &model.Post{ - ChannelId: post.ChannelId, - Message: c.T("api.post.disabled_all", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}), - CreateAt: post.CreateAt + 1, - }, - ) - } - - if hereNotification { - if result := <-Srv.Store.Status().GetOnline(); result.Err != nil { - l4g.Warn(utils.T("api.post.notification.here.warn"), result.Err) - return nil - } else { - statuses := result.Data.([]*model.Status) + // If the channel has more than 1K users then @here is disabled + if hereNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { + hereNotification = false + SendEphemeralPost( + c.TeamId, + post.UserId, + &model.Post{ + ChannelId: post.ChannelId, + Message: c.T("api.post.disabled_here", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}), + CreateAt: post.CreateAt + 1, + }, + ) + } + + // If the channel has more than 1K users then @channel is disabled + if channelNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { + SendEphemeralPost( + c.TeamId, + post.UserId, + &model.Post{ + ChannelId: post.ChannelId, + Message: c.T("api.post.disabled_channel", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}), + CreateAt: post.CreateAt + 1, + }, + ) + } + + // If the channel has more than 1K users then @all is disabled + if allNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel { + SendEphemeralPost( + c.TeamId, + post.UserId, + &model.Post{ + ChannelId: post.ChannelId, + Message: c.T("api.post.disabled_all", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}), + CreateAt: post.CreateAt + 1, + }, + ) + } + + if hereNotification { + statuses := GetAllStatuses() for _, status := range statuses { if status.UserId == post.UserId { continue @@ -771,43 +806,29 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * } } } - } - - // Make sure all mention updates are complete to prevent race - // Probably better to batch these DB updates in the future - // MUST be completed before push notifications send - for _, uchan := range updateMentionChans { - if result := <-uchan; result.Err != nil { - l4g.Warn(utils.T("api.post.update_mention_count_and_forget.update_error"), post.Id, post.ChannelId, result.Err) - } - } - - sendPushNotifications := false - if *utils.Cfg.EmailSettings.SendPushNotifications { - pushServer := *utils.Cfg.EmailSettings.PushNotificationServer - if pushServer == model.MHPNS && (!utils.IsLicensed || !*utils.License.Features.MHPNS) { - l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn")) - sendPushNotifications = false - } else { - sendPushNotifications = true - } - } - if sendPushNotifications { - for _, id := range mentionedUsersList { - var status *model.Status - var err *model.AppError - if status, err = GetStatus(id); err != nil { - status = &model.Status{id, model.STATUS_OFFLINE, false, 0, ""} + // Make sure all mention updates are complete to prevent race + // Probably better to batch these DB updates in the future + // MUST be completed before push notifications send + for _, uchan := range updateMentionChans { + if result := <-uchan; result.Err != nil { + l4g.Warn(utils.T("api.post.update_mention_count_and_forget.update_error"), post.Id, post.ChannelId, result.Err) } + } - if DoesStatusAllowPushNotification(profileMap[id], status, post.ChannelId) { - sendPushNotification(post, profileMap[id], channel, senderName[id], true) + sendPushNotifications := false + if *utils.Cfg.EmailSettings.SendPushNotifications { + pushServer := *utils.Cfg.EmailSettings.PushNotificationServer + if pushServer == model.MHPNS && (!utils.IsLicensed || !*utils.License.Features.MHPNS) { + l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn")) + sendPushNotifications = false + } else { + sendPushNotifications = true } } - for _, id := range allActivityPushUserIds { - if _, ok := mentionedUserIds[id]; !ok { + if sendPushNotifications { + for _, id := range mentionedUsersList { var status *model.Status var err *model.AppError if status, err = GetStatus(id); err != nil { @@ -815,7 +836,21 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * } if DoesStatusAllowPushNotification(profileMap[id], status, post.ChannelId) { - sendPushNotification(post, profileMap[id], channel, senderName[id], false) + sendPushNotification(post, profileMap[id], channel, senderName[id], true) + } + } + + for _, id := range allActivityPushUserIds { + if _, ok := mentionedUserIds[id]; !ok { + var status *model.Status + var err *model.AppError + if status, err = GetStatus(id); err != nil { + status = &model.Status{id, model.STATUS_OFFLINE, false, 0, ""} + } + + if DoesStatusAllowPushNotification(profileMap[id], status, post.ChannelId) { + sendPushNotification(post, profileMap[id], channel, senderName[id], false) + } } } } @@ -829,7 +864,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel * message.Add("sender_name", senderUsername) message.Add("team_id", team.Id) - if len(post.FileIds) != 0 { + if len(post.FileIds) != 0 && fchan != nil { message.Add("otherFile", "true") var infos []*model.FileInfo @@ -874,15 +909,18 @@ func sendNotificationEmail(c *Context, post *model.Post, user *model.User, chann for i := range teams { if teams[i].Id == team.Id { found = true + team = teams[i] break } } - if !found && len(teams) > 0 { - team = teams[0] - } else { - // in case the user hasn't joined any teams we send them to the select_team page - team = &model.Team{Name: "select_team", DisplayName: utils.Cfg.TeamSettings.SiteName} + if !found { + if len(teams) > 0 { + team = teams[0] + } else { + // in case the user hasn't joined any teams we send them to the select_team page + team = &model.Team{Name: "select_team", DisplayName: utils.Cfg.TeamSettings.SiteName} + } } } } diff --git a/api/status.go b/api/status.go index 99d5bc417..909ab50ec 100644 --- a/api/status.go +++ b/api/status.go @@ -42,38 +42,31 @@ func InitStatus() { } func getStatusesHttp(c *Context, w http.ResponseWriter, r *http.Request) { - statusMap, err := GetAllStatuses() - if err != nil { - c.Err = err - return - } - + statusMap := model.StatusMapToInterfaceMap(GetAllStatuses()) w.Write([]byte(model.StringInterfaceToJson(statusMap))) } func getStatusesWebSocket(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) { - statusMap, err := GetAllStatuses() - if err != nil { - return nil, err - } - - return statusMap, nil + statusMap := GetAllStatuses() + return model.StatusMapToInterfaceMap(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 - } else { - statuses := result.Data.([]*model.Status) +func GetAllStatuses() map[string]*model.Status { + userIds := statusCache.Keys() + statusMap := map[string]*model.Status{} - statusMap := map[string]interface{}{} - for _, s := range statuses { - statusMap[s.UserId] = s.Status + for _, userId := range userIds { + if id, ok := userId.(string); !ok { + continue + } else { + status := GetStatusFromCache(id) + if status != nil { + statusMap[id] = status + } } - - return statusMap, nil } + + return statusMap } func getStatusesByIdsHttp(c *Context, w http.ResponseWriter, r *http.Request) { @@ -268,12 +261,21 @@ func SetStatusAwayIfNeeded(userId string, manual bool) { go Publish(event) } -func GetStatus(userId string) (*model.Status, *model.AppError) { +func GetStatusFromCache(userId string) *model.Status { if result, ok := statusCache.Get(userId); ok { status := result.(*model.Status) statusCopy := &model.Status{} *statusCopy = *status - return statusCopy, nil + return statusCopy + } + + return nil +} + +func GetStatus(userId string) (*model.Status, *model.AppError) { + status := GetStatusFromCache(userId) + if status != nil { + return status, nil } if result := <-Srv.Store.Status().Get(userId); result.Err != nil { diff --git a/api/team.go b/api/team.go index c17bf87af..c855a515d 100644 --- a/api/team.go +++ b/api/team.go @@ -48,7 +48,7 @@ func InitTeam() { // These should be moved to the global admin console BaseRoutes.NeedTeam.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST") - BaseRoutes.Teams.Handle("/add_user_to_team_from_invite", ApiUserRequired(addUserToTeamFromInvite)).Methods("POST") + BaseRoutes.Teams.Handle("/add_user_to_team_from_invite", ApiUserRequiredMfa(addUserToTeamFromInvite)).Methods("POST") } func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { @@ -378,7 +378,7 @@ func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { emailList = append(emailList, invite["email"]) } - InviteMembers(team, user.GetDisplayName(), emailList) + InviteMembers(team, user.GetDisplayName(), emailList, c.GetSiteURL()) w.Write([]byte(invites.ToJson())) } @@ -640,7 +640,7 @@ func getMyTeamsUnread(c *Context, w http.ResponseWriter, r *http.Request) { } } -func InviteMembers(team *model.Team, senderName string, invites []string) { +func InviteMembers(team *model.Team, senderName string, invites []string, siteURL string) { for _, invite := range invites { if len(invite) > 0 { senderRole := utils.T("api.team.invite_members.member") @@ -649,13 +649,13 @@ func InviteMembers(team *model.Team, senderName string, invites []string) { map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName, "SiteName": utils.ClientCfg["SiteName"]}) bodyPage := utils.NewHTMLTemplate("invite_body", model.DEFAULT_LOCALE) - bodyPage.Props["SiteURL"] = *utils.Cfg.ServiceSettings.SiteURL + bodyPage.Props["SiteURL"] = siteURL bodyPage.Props["Title"] = utils.T("api.templates.invite_body.title") bodyPage.Html["Info"] = template.HTML(utils.T("api.templates.invite_body.info", map[string]interface{}{"SenderStatus": senderRole, "SenderName": senderName, "TeamDisplayName": team.DisplayName})) bodyPage.Props["Button"] = utils.T("api.templates.invite_body.button") bodyPage.Html["ExtraInfo"] = template.HTML(utils.T("api.templates.invite_body.extra_info", - map[string]interface{}{"TeamDisplayName": team.DisplayName, "TeamURL": *utils.Cfg.ServiceSettings.SiteURL + "/" + team.Name})) + map[string]interface{}{"TeamDisplayName": team.DisplayName, "TeamURL": siteURL + "/" + team.Name})) props := make(map[string]string) props["email"] = invite @@ -665,7 +665,7 @@ func InviteMembers(team *model.Team, senderName string, invites []string) { props["time"] = fmt.Sprintf("%v", model.GetMillis()) data := model.MapToJson(props) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) - bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", *utils.Cfg.ServiceSettings.SiteURL, url.QueryEscape(data), url.QueryEscape(hash)) + bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", siteURL, url.QueryEscape(data), url.QueryEscape(hash)) if !utils.Cfg.EmailSettings.SendEmailNotifications { l4g.Info(utils.T("api.team.invite_members.sending.info"), invite, bodyPage.Props["Link"]) |