mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
On page load, we load ALL channels and channel members from all teams. But then, on team_switch, we would again load channels and channel members from that team. This was redundant and mainly kept because previously the websocket events were considered unreliable. Now with reliable websockets, and client-side pings, we can detect broken connections faster and recover without loss. Additionally, the getAllChannelMembers call would page through all responses on the client side. This was inefficient and incur extra latency. To optimize for this, we introduce server-side streaming of the full response if page is set to -1. This optimizes the intial response as well. https://mattermost.atlassian.net/browse/MM-56906 ```release-note Optimize team switch operation by removing calls to get channels and channel members. ``` Co-authored-by: Mattermost Build <[email protected]>
245 lines
9.1 KiB
Go
245 lines
9.1 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
const (
|
|
ChannelNotifyDefault = "default"
|
|
ChannelNotifyAll = "all"
|
|
ChannelNotifyMention = "mention"
|
|
ChannelNotifyNone = "none"
|
|
ChannelMarkUnreadAll = "all"
|
|
ChannelMarkUnreadMention = "mention"
|
|
IgnoreChannelMentionsDefault = "default"
|
|
IgnoreChannelMentionsOff = "off"
|
|
IgnoreChannelMentionsOn = "on"
|
|
IgnoreChannelMentionsNotifyProp = "ignore_channel_mentions"
|
|
ChannelAutoFollowThreadsOff = "off"
|
|
ChannelAutoFollowThreadsOn = "on"
|
|
ChannelAutoFollowThreads = "channel_auto_follow_threads"
|
|
ChannelMemberNotifyPropsMaxRunes = 800000
|
|
)
|
|
|
|
type ChannelUnread struct {
|
|
TeamId string `json:"team_id"`
|
|
ChannelId string `json:"channel_id"`
|
|
MsgCount int64 `json:"msg_count"`
|
|
MentionCount int64 `json:"mention_count"`
|
|
MentionCountRoot int64 `json:"mention_count_root"`
|
|
UrgentMentionCount int64 `json:"urgent_mention_count"`
|
|
MsgCountRoot int64 `json:"msg_count_root"`
|
|
NotifyProps StringMap `json:"-"`
|
|
}
|
|
|
|
type ChannelUnreadAt struct {
|
|
TeamId string `json:"team_id"`
|
|
UserId string `json:"user_id"`
|
|
ChannelId string `json:"channel_id"`
|
|
MsgCount int64 `json:"msg_count"`
|
|
MentionCount int64 `json:"mention_count"`
|
|
MentionCountRoot int64 `json:"mention_count_root"`
|
|
UrgentMentionCount int64 `json:"urgent_mention_count"`
|
|
MsgCountRoot int64 `json:"msg_count_root"`
|
|
LastViewedAt int64 `json:"last_viewed_at"`
|
|
NotifyProps StringMap `json:"-"`
|
|
}
|
|
|
|
type ChannelMember struct {
|
|
ChannelId string `json:"channel_id"`
|
|
UserId string `json:"user_id"`
|
|
Roles string `json:"roles"`
|
|
LastViewedAt int64 `json:"last_viewed_at"`
|
|
MsgCount int64 `json:"msg_count"`
|
|
MentionCount int64 `json:"mention_count"`
|
|
MentionCountRoot int64 `json:"mention_count_root"`
|
|
UrgentMentionCount int64 `json:"urgent_mention_count"`
|
|
MsgCountRoot int64 `json:"msg_count_root"`
|
|
NotifyProps StringMap `json:"notify_props"`
|
|
LastUpdateAt int64 `json:"last_update_at"`
|
|
SchemeGuest bool `json:"scheme_guest"`
|
|
SchemeUser bool `json:"scheme_user"`
|
|
SchemeAdmin bool `json:"scheme_admin"`
|
|
ExplicitRoles string `json:"explicit_roles"`
|
|
}
|
|
|
|
func (o *ChannelMember) Auditable() map[string]any {
|
|
return map[string]any{
|
|
"channel_id": o.ChannelId,
|
|
"user_id": o.UserId,
|
|
"roles": o.Roles,
|
|
"last_viewed_at": o.LastViewedAt,
|
|
"msg_count": o.MsgCount,
|
|
"mention_count": o.MentionCount,
|
|
"mention_count_root": o.MentionCountRoot,
|
|
"urgent_mention_count": o.UrgentMentionCount,
|
|
"msg_count_root": o.MsgCountRoot,
|
|
"notify_props": o.NotifyProps,
|
|
"last_update_at": o.LastUpdateAt,
|
|
"scheme_guest": o.SchemeGuest,
|
|
"scheme_user": o.SchemeUser,
|
|
"scheme_admin": o.SchemeAdmin,
|
|
"explicit_roles": o.ExplicitRoles,
|
|
}
|
|
}
|
|
|
|
// ChannelMemberWithTeamData contains ChannelMember appended with extra team information
|
|
// as well.
|
|
type ChannelMemberWithTeamData struct {
|
|
ChannelMember
|
|
TeamDisplayName string `json:"team_display_name"`
|
|
TeamName string `json:"team_name"`
|
|
TeamUpdateAt int64 `json:"team_update_at"`
|
|
}
|
|
|
|
type ChannelMembers []ChannelMember
|
|
|
|
type ChannelMembersWithTeamData []ChannelMemberWithTeamData
|
|
|
|
type ChannelMemberForExport struct {
|
|
ChannelMember
|
|
ChannelName string
|
|
Username string
|
|
}
|
|
|
|
type ChannelMemberCursor struct {
|
|
Page int // If page is -1, then FromChannelID is used as a cursor.
|
|
PerPage int
|
|
FromChannelID string
|
|
}
|
|
|
|
func (o *ChannelMember) IsValid() *AppError {
|
|
if !IsValidId(o.ChannelId) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if !IsValidId(o.UserId) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if appErr := IsChannelMemberNotifyPropsValid(o.NotifyProps, false); appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
if len(o.Roles) > UserRolesMaxLength {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.roles_limit.app_error",
|
|
map[string]any{"Limit": UserRolesMaxLength}, "", http.StatusBadRequest)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IsChannelMemberNotifyPropsValid(notifyProps map[string]string, allowMissingFields bool) *AppError {
|
|
if notifyLevel, ok := notifyProps[DesktopNotifyProp]; ok || !allowMissingFields {
|
|
if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error", nil, "notify_level="+notifyLevel, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if markUnreadLevel, ok := notifyProps[MarkUnreadNotifyProp]; ok || !allowMissingFields {
|
|
if len(markUnreadLevel) > 20 || !IsChannelMarkUnreadLevelValid(markUnreadLevel) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.unread_level.app_error", nil, "mark_unread_level="+markUnreadLevel, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if pushLevel, ok := notifyProps[PushNotifyProp]; ok {
|
|
if len(pushLevel) > 20 || !IsChannelNotifyLevelValid(pushLevel) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.push_level.app_error", nil, "push_notification_level="+pushLevel, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if sendEmail, ok := notifyProps[EmailNotifyProp]; ok {
|
|
if len(sendEmail) > 20 || !IsSendEmailValid(sendEmail) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.email_value.app_error", nil, "push_notification_level="+sendEmail, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if ignoreChannelMentions, ok := notifyProps[IgnoreChannelMentionsNotifyProp]; ok {
|
|
if len(ignoreChannelMentions) > 40 || !IsIgnoreChannelMentionsValid(ignoreChannelMentions) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.ignore_channel_mentions_value.app_error", nil, "ignore_channel_mentions="+ignoreChannelMentions, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if channelAutoFollowThreads, ok := notifyProps[ChannelAutoFollowThreads]; ok {
|
|
if len(channelAutoFollowThreads) > 3 || !IsChannelAutoFollowThreadsValid(channelAutoFollowThreads) {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.channel_auto_follow_threads_value.app_error", nil, "channel_auto_follow_threads="+channelAutoFollowThreads, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
jsonStringNotifyProps := string(ToJSON(notifyProps))
|
|
if utf8.RuneCountInString(jsonStringNotifyProps) > ChannelMemberNotifyPropsMaxRunes {
|
|
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_props.app_error", nil, fmt.Sprint("length=", utf8.RuneCountInString(jsonStringNotifyProps)), http.StatusBadRequest)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o *ChannelMember) PreSave() {
|
|
o.LastUpdateAt = GetMillis()
|
|
}
|
|
|
|
func (o *ChannelMember) PreUpdate() {
|
|
o.LastUpdateAt = GetMillis()
|
|
}
|
|
|
|
func (o *ChannelMember) GetRoles() []string {
|
|
return strings.Fields(o.Roles)
|
|
}
|
|
|
|
func (o *ChannelMember) SetChannelMuted(muted bool) {
|
|
if o.IsChannelMuted() {
|
|
o.NotifyProps[MarkUnreadNotifyProp] = ChannelMarkUnreadAll
|
|
} else {
|
|
o.NotifyProps[MarkUnreadNotifyProp] = ChannelMarkUnreadMention
|
|
}
|
|
}
|
|
|
|
func (o *ChannelMember) IsChannelMuted() bool {
|
|
return o.NotifyProps[MarkUnreadNotifyProp] == ChannelMarkUnreadMention
|
|
}
|
|
|
|
func IsChannelNotifyLevelValid(notifyLevel string) bool {
|
|
return notifyLevel == ChannelNotifyDefault ||
|
|
notifyLevel == ChannelNotifyAll ||
|
|
notifyLevel == ChannelNotifyMention ||
|
|
notifyLevel == ChannelNotifyNone
|
|
}
|
|
|
|
func IsChannelMarkUnreadLevelValid(markUnreadLevel string) bool {
|
|
return markUnreadLevel == ChannelMarkUnreadAll || markUnreadLevel == ChannelMarkUnreadMention
|
|
}
|
|
|
|
func IsSendEmailValid(sendEmail string) bool {
|
|
return sendEmail == ChannelNotifyDefault || sendEmail == "true" || sendEmail == "false"
|
|
}
|
|
|
|
func IsIgnoreChannelMentionsValid(ignoreChannelMentions string) bool {
|
|
return ignoreChannelMentions == IgnoreChannelMentionsOn || ignoreChannelMentions == IgnoreChannelMentionsOff || ignoreChannelMentions == IgnoreChannelMentionsDefault
|
|
}
|
|
|
|
func IsChannelAutoFollowThreadsValid(channelAutoFollowThreads string) bool {
|
|
return channelAutoFollowThreads == ChannelAutoFollowThreadsOn || channelAutoFollowThreads == ChannelAutoFollowThreadsOff
|
|
}
|
|
|
|
func GetDefaultChannelNotifyProps() StringMap {
|
|
return StringMap{
|
|
DesktopNotifyProp: ChannelNotifyDefault,
|
|
MarkUnreadNotifyProp: ChannelMarkUnreadAll,
|
|
PushNotifyProp: ChannelNotifyDefault,
|
|
EmailNotifyProp: ChannelNotifyDefault,
|
|
IgnoreChannelMentionsNotifyProp: IgnoreChannelMentionsDefault,
|
|
ChannelAutoFollowThreads: ChannelAutoFollowThreadsOff,
|
|
}
|
|
}
|
|
|
|
type ChannelMemberIdentifier struct {
|
|
ChannelId string `json:"channel_id"`
|
|
UserId string `json:"user_id"`
|
|
}
|