1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package model 5 6import ( 7 "crypto/sha1" 8 "encoding/hex" 9 "io" 10 "net/http" 11 "sort" 12 "strings" 13 "unicode/utf8" 14) 15 16type ChannelType string 17 18const ( 19 ChannelTypeOpen ChannelType = "O" 20 ChannelTypePrivate ChannelType = "P" 21 ChannelTypeDirect ChannelType = "D" 22 ChannelTypeGroup ChannelType = "G" 23 24 ChannelGroupMaxUsers = 8 25 ChannelGroupMinUsers = 3 26 DefaultChannelName = "town-square" 27 ChannelDisplayNameMaxRunes = 64 28 ChannelNameMinLength = 2 29 ChannelNameMaxLength = 64 30 ChannelHeaderMaxRunes = 1024 31 ChannelPurposeMaxRunes = 250 32 ChannelCacheSize = 25000 33 34 ChannelSortByUsername = "username" 35 ChannelSortByStatus = "status" 36) 37 38type Channel struct { 39 Id string `json:"id"` 40 CreateAt int64 `json:"create_at"` 41 UpdateAt int64 `json:"update_at"` 42 DeleteAt int64 `json:"delete_at"` 43 TeamId string `json:"team_id"` 44 Type ChannelType `json:"type"` 45 DisplayName string `json:"display_name"` 46 Name string `json:"name"` 47 Header string `json:"header"` 48 Purpose string `json:"purpose"` 49 LastPostAt int64 `json:"last_post_at"` 50 TotalMsgCount int64 `json:"total_msg_count"` 51 ExtraUpdateAt int64 `json:"extra_update_at"` 52 CreatorId string `json:"creator_id"` 53 SchemeId *string `json:"scheme_id"` 54 Props map[string]interface{} `json:"props" db:"-"` 55 GroupConstrained *bool `json:"group_constrained"` 56 Shared *bool `json:"shared"` 57 TotalMsgCountRoot int64 `json:"total_msg_count_root"` 58 PolicyID *string `json:"policy_id" db:"-"` 59} 60 61type ChannelWithTeamData struct { 62 Channel 63 TeamDisplayName string `json:"team_display_name"` 64 TeamName string `json:"team_name"` 65 TeamUpdateAt int64 `json:"team_update_at"` 66} 67 68type ChannelsWithCount struct { 69 Channels ChannelListWithTeamData `json:"channels"` 70 TotalCount int64 `json:"total_count"` 71} 72 73type ChannelPatch struct { 74 DisplayName *string `json:"display_name"` 75 Name *string `json:"name"` 76 Header *string `json:"header"` 77 Purpose *string `json:"purpose"` 78 GroupConstrained *bool `json:"group_constrained"` 79} 80 81type ChannelForExport struct { 82 Channel 83 TeamName string 84 SchemeName *string 85} 86 87type DirectChannelForExport struct { 88 Channel 89 Members *[]string 90} 91 92type ChannelModeration struct { 93 Name string `json:"name"` 94 Roles *ChannelModeratedRoles `json:"roles"` 95} 96 97type ChannelModeratedRoles struct { 98 Guests *ChannelModeratedRole `json:"guests"` 99 Members *ChannelModeratedRole `json:"members"` 100} 101 102type ChannelModeratedRole struct { 103 Value bool `json:"value"` 104 Enabled bool `json:"enabled"` 105} 106 107type ChannelModerationPatch struct { 108 Name *string `json:"name"` 109 Roles *ChannelModeratedRolesPatch `json:"roles"` 110} 111 112type ChannelModeratedRolesPatch struct { 113 Guests *bool `json:"guests"` 114 Members *bool `json:"members"` 115} 116 117// ChannelSearchOpts contains options for searching channels. 118// 119// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records. 120// ExcludeDefaultChannels will exclude the configured default channels (ex 'town-square' and 'off-topic'). 121// IncludeDeleted will include channel records where DeleteAt != 0. 122// ExcludeChannelNames will exclude channels from the results by name. 123// Paginate whether to paginate the results. 124// Page page requested, if results are paginated. 125// PerPage number of results per page, if paginated. 126// 127type ChannelSearchOpts struct { 128 NotAssociatedToGroup string 129 ExcludeDefaultChannels bool 130 IncludeDeleted bool 131 Deleted bool 132 ExcludeChannelNames []string 133 TeamIds []string 134 GroupConstrained bool 135 ExcludeGroupConstrained bool 136 PolicyID string 137 ExcludePolicyConstrained bool 138 IncludePolicyID bool 139 Public bool 140 Private bool 141 Page *int 142 PerPage *int 143} 144 145type ChannelMemberCountByGroup struct { 146 GroupId string `db:"-" json:"group_id"` 147 ChannelMemberCount int64 `db:"-" json:"channel_member_count"` 148 ChannelMemberTimezonesCount int64 `db:"-" json:"channel_member_timezones_count"` 149} 150 151type ChannelOption func(channel *Channel) 152 153func WithID(ID string) ChannelOption { 154 return func(channel *Channel) { 155 channel.Id = ID 156 } 157} 158 159func (o *Channel) DeepCopy() *Channel { 160 copy := *o 161 if copy.SchemeId != nil { 162 copy.SchemeId = NewString(*o.SchemeId) 163 } 164 return © 165} 166 167func (o *Channel) Etag() string { 168 return Etag(o.Id, o.UpdateAt) 169} 170 171func (o *Channel) IsValid() *AppError { 172 if !IsValidId(o.Id) { 173 return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest) 174 } 175 176 if o.CreateAt == 0 { 177 return NewAppError("Channel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) 178 } 179 180 if o.UpdateAt == 0 { 181 return NewAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) 182 } 183 184 if utf8.RuneCountInString(o.DisplayName) > ChannelDisplayNameMaxRunes { 185 return NewAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id, http.StatusBadRequest) 186 } 187 188 if !IsValidChannelIdentifier(o.Name) { 189 return NewAppError("Channel.IsValid", "model.channel.is_valid.2_or_more.app_error", nil, "id="+o.Id, http.StatusBadRequest) 190 } 191 192 if !(o.Type == ChannelTypeOpen || o.Type == ChannelTypePrivate || o.Type == ChannelTypeDirect || o.Type == ChannelTypeGroup) { 193 return NewAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest) 194 } 195 196 if utf8.RuneCountInString(o.Header) > ChannelHeaderMaxRunes { 197 return NewAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id, http.StatusBadRequest) 198 } 199 200 if utf8.RuneCountInString(o.Purpose) > ChannelPurposeMaxRunes { 201 return NewAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id, http.StatusBadRequest) 202 } 203 204 if len(o.CreatorId) > 26 { 205 return NewAppError("Channel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "", http.StatusBadRequest) 206 } 207 208 userIds := strings.Split(o.Name, "__") 209 if o.Type != ChannelTypeDirect && len(userIds) == 2 && IsValidId(userIds[0]) && IsValidId(userIds[1]) { 210 return NewAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "", http.StatusBadRequest) 211 } 212 213 return nil 214} 215 216func (o *Channel) PreSave() { 217 if o.Id == "" { 218 o.Id = NewId() 219 } 220 221 o.Name = SanitizeUnicode(o.Name) 222 o.DisplayName = SanitizeUnicode(o.DisplayName) 223 224 o.CreateAt = GetMillis() 225 o.UpdateAt = o.CreateAt 226 o.ExtraUpdateAt = 0 227} 228 229func (o *Channel) PreUpdate() { 230 o.UpdateAt = GetMillis() 231 o.Name = SanitizeUnicode(o.Name) 232 o.DisplayName = SanitizeUnicode(o.DisplayName) 233} 234 235func (o *Channel) IsGroupOrDirect() bool { 236 return o.Type == ChannelTypeDirect || o.Type == ChannelTypeGroup 237} 238 239func (o *Channel) IsOpen() bool { 240 return o.Type == ChannelTypeOpen 241} 242 243func (o *Channel) Patch(patch *ChannelPatch) { 244 if patch.DisplayName != nil { 245 o.DisplayName = *patch.DisplayName 246 } 247 248 if patch.Name != nil { 249 o.Name = *patch.Name 250 } 251 252 if patch.Header != nil { 253 o.Header = *patch.Header 254 } 255 256 if patch.Purpose != nil { 257 o.Purpose = *patch.Purpose 258 } 259 260 if patch.GroupConstrained != nil { 261 o.GroupConstrained = patch.GroupConstrained 262 } 263} 264 265func (o *Channel) MakeNonNil() { 266 if o.Props == nil { 267 o.Props = make(map[string]interface{}) 268 } 269} 270 271func (o *Channel) AddProp(key string, value interface{}) { 272 o.MakeNonNil() 273 274 o.Props[key] = value 275} 276 277func (o *Channel) IsGroupConstrained() bool { 278 return o.GroupConstrained != nil && *o.GroupConstrained 279} 280 281func (o *Channel) IsShared() bool { 282 return o.Shared != nil && *o.Shared 283} 284 285func (o *Channel) GetOtherUserIdForDM(userId string) string { 286 if o.Type != ChannelTypeDirect { 287 return "" 288 } 289 290 userIds := strings.Split(o.Name, "__") 291 292 var otherUserId string 293 294 if userIds[0] != userIds[1] { 295 if userIds[0] == userId { 296 otherUserId = userIds[1] 297 } else { 298 otherUserId = userIds[0] 299 } 300 } 301 302 return otherUserId 303} 304 305func GetDMNameFromIds(userId1, userId2 string) string { 306 if userId1 > userId2 { 307 return userId2 + "__" + userId1 308 } 309 return userId1 + "__" + userId2 310} 311 312func GetGroupDisplayNameFromUsers(users []*User, truncate bool) string { 313 usernames := make([]string, len(users)) 314 for index, user := range users { 315 usernames[index] = user.Username 316 } 317 318 sort.Strings(usernames) 319 320 name := strings.Join(usernames, ", ") 321 322 if truncate && len(name) > ChannelNameMaxLength { 323 name = name[:ChannelNameMaxLength] 324 } 325 326 return name 327} 328 329func GetGroupNameFromUserIds(userIds []string) string { 330 sort.Strings(userIds) 331 332 h := sha1.New() 333 for _, id := range userIds { 334 io.WriteString(h, id) 335 } 336 337 return hex.EncodeToString(h.Sum(nil)) 338} 339