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 &copy
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