* Excludes remote channels from channel search This is done through a new body parameter in the SearchAllChannels endpoint that allows to search for local only channels, which are either channels that are shared but marked as homed locally, or channels that are not shared at all. * fix lint * Fix tests --------- Co-authored-by: Caleb Roseland <caleb@calebroseland.com>
422 строки
11 KiB
Go
422 строки
11 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type ChannelType string
|
|
|
|
const (
|
|
ChannelTypeOpen ChannelType = "O"
|
|
ChannelTypePrivate ChannelType = "P"
|
|
ChannelTypeDirect ChannelType = "D"
|
|
ChannelTypeGroup ChannelType = "G"
|
|
|
|
ChannelGroupMaxUsers = 8
|
|
ChannelGroupMinUsers = 3
|
|
DefaultChannelName = "town-square"
|
|
ChannelDisplayNameMaxRunes = 64
|
|
ChannelNameMinLength = 1
|
|
ChannelNameMaxLength = 64
|
|
ChannelHeaderMaxRunes = 1024
|
|
ChannelPurposeMaxRunes = 250
|
|
ChannelCacheSize = 25000
|
|
|
|
ChannelSortByUsername = "username"
|
|
ChannelSortByStatus = "status"
|
|
)
|
|
|
|
type Channel struct {
|
|
Id string `json:"id"`
|
|
CreateAt int64 `json:"create_at"`
|
|
UpdateAt int64 `json:"update_at"`
|
|
DeleteAt int64 `json:"delete_at"`
|
|
TeamId string `json:"team_id"`
|
|
Type ChannelType `json:"type"`
|
|
DisplayName string `json:"display_name"`
|
|
Name string `json:"name"`
|
|
Header string `json:"header"`
|
|
Purpose string `json:"purpose"`
|
|
LastPostAt int64 `json:"last_post_at"`
|
|
TotalMsgCount int64 `json:"total_msg_count"`
|
|
ExtraUpdateAt int64 `json:"extra_update_at"`
|
|
CreatorId string `json:"creator_id"`
|
|
SchemeId *string `json:"scheme_id"`
|
|
Props map[string]any `json:"props"`
|
|
GroupConstrained *bool `json:"group_constrained"`
|
|
Shared *bool `json:"shared"`
|
|
TotalMsgCountRoot int64 `json:"total_msg_count_root"`
|
|
PolicyID *string `json:"policy_id"`
|
|
LastRootPostAt int64 `json:"last_root_post_at"`
|
|
}
|
|
|
|
func (o *Channel) Auditable() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"create_at": o.CreateAt,
|
|
"creator_id": o.CreatorId,
|
|
"delete_at": o.DeleteAt,
|
|
"extra_group_at": o.ExtraUpdateAt,
|
|
"group_constrained": o.GroupConstrained,
|
|
"id": o.Id,
|
|
"last_post_at": o.LastPostAt,
|
|
"last_root_post_at": o.LastRootPostAt,
|
|
"policy_id": o.PolicyID,
|
|
"props": o.Props,
|
|
"scheme_id": o.SchemeId,
|
|
"shared": o.Shared,
|
|
"team_id": o.TeamId,
|
|
"total_msg_count_root": o.TotalMsgCountRoot,
|
|
"type": o.Type,
|
|
"update_at": o.UpdateAt,
|
|
}
|
|
}
|
|
|
|
func (o *Channel) LogClone() any {
|
|
return o.Auditable()
|
|
}
|
|
|
|
type ChannelWithTeamData struct {
|
|
Channel
|
|
TeamDisplayName string `json:"team_display_name"`
|
|
TeamName string `json:"team_name"`
|
|
TeamUpdateAt int64 `json:"team_update_at"`
|
|
}
|
|
|
|
type ChannelsWithCount struct {
|
|
Channels ChannelListWithTeamData `json:"channels"`
|
|
TotalCount int64 `json:"total_count"`
|
|
}
|
|
|
|
type ChannelPatch struct {
|
|
DisplayName *string `json:"display_name"`
|
|
Name *string `json:"name"`
|
|
Header *string `json:"header"`
|
|
Purpose *string `json:"purpose"`
|
|
GroupConstrained *bool `json:"group_constrained"`
|
|
}
|
|
|
|
func (c *ChannelPatch) Auditable() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"header": c.Header,
|
|
"group_constrained": c.GroupConstrained,
|
|
"purpose": c.Purpose,
|
|
}
|
|
}
|
|
|
|
type ChannelForExport struct {
|
|
Channel
|
|
TeamName string
|
|
SchemeName *string
|
|
}
|
|
|
|
type DirectChannelForExport struct {
|
|
Channel
|
|
Members []*ChannelMemberForExport
|
|
}
|
|
|
|
type ChannelModeration struct {
|
|
Name string `json:"name"`
|
|
Roles *ChannelModeratedRoles `json:"roles"`
|
|
}
|
|
|
|
type ChannelModeratedRoles struct {
|
|
Guests *ChannelModeratedRole `json:"guests"`
|
|
Members *ChannelModeratedRole `json:"members"`
|
|
}
|
|
|
|
type ChannelModeratedRole struct {
|
|
Value bool `json:"value"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
type ChannelModerationPatch struct {
|
|
Name *string `json:"name"`
|
|
Roles *ChannelModeratedRolesPatch `json:"roles"`
|
|
}
|
|
|
|
func (c *ChannelModerationPatch) Auditable() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"name": c.Name,
|
|
"roles": c.Roles,
|
|
}
|
|
}
|
|
|
|
type ChannelModeratedRolesPatch struct {
|
|
Guests *bool `json:"guests"`
|
|
Members *bool `json:"members"`
|
|
}
|
|
|
|
// ChannelSearchOpts contains options for searching channels.
|
|
//
|
|
// NotAssociatedToGroup will exclude channels that have associated, active GroupChannels records.
|
|
// ExcludeDefaultChannels will exclude the configured default channels (ex 'town-square' and 'off-topic').
|
|
// IncludeDeleted will include channel records where DeleteAt != 0.
|
|
// ExcludeChannelNames will exclude channels from the results by name.
|
|
// IncludeSearchById will include searching matches against channel IDs in the results
|
|
// Paginate whether to paginate the results.
|
|
// Page page requested, if results are paginated.
|
|
// PerPage number of results per page, if paginated.
|
|
type ChannelSearchOpts struct {
|
|
NotAssociatedToGroup string
|
|
ExcludeDefaultChannels bool
|
|
IncludeDeleted bool // If true, deleted channels will be included in the results.
|
|
Deleted bool
|
|
ExcludeChannelNames []string
|
|
TeamIds []string
|
|
GroupConstrained bool
|
|
ExcludeGroupConstrained bool
|
|
PolicyID string
|
|
ExcludePolicyConstrained bool
|
|
IncludePolicyID bool
|
|
IncludeSearchById bool
|
|
ExcludeRemote bool
|
|
Public bool
|
|
Private bool
|
|
Page *int
|
|
PerPage *int
|
|
LastDeleteAt int // When combined with IncludeDeleted, only channels deleted after this time will be returned.
|
|
LastUpdateAt int
|
|
}
|
|
|
|
type ChannelMemberCountByGroup struct {
|
|
GroupId string `json:"group_id"`
|
|
ChannelMemberCount int64 `json:"channel_member_count"`
|
|
ChannelMemberTimezonesCount int64 `json:"channel_member_timezones_count"`
|
|
}
|
|
|
|
type ChannelOption func(channel *Channel)
|
|
|
|
var gmNameRegex = regexp.MustCompile("^[a-f0-9]{40}$")
|
|
|
|
func WithID(ID string) ChannelOption {
|
|
return func(channel *Channel) {
|
|
channel.Id = ID
|
|
}
|
|
}
|
|
|
|
func (o *Channel) DeepCopy() *Channel {
|
|
cCopy := *o
|
|
if cCopy.SchemeId != nil {
|
|
cCopy.SchemeId = NewPointer(*o.SchemeId)
|
|
}
|
|
return &cCopy
|
|
}
|
|
|
|
func (o *Channel) Etag() string {
|
|
return Etag(o.Id, o.UpdateAt)
|
|
}
|
|
|
|
func (o *Channel) IsValid() *AppError {
|
|
if !IsValidId(o.Id) {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if o.CreateAt == 0 {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if o.UpdateAt == 0 {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if utf8.RuneCountInString(o.DisplayName) > ChannelDisplayNameMaxRunes {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if !IsValidChannelIdentifier(o.Name) {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.1_or_more.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if !(o.Type == ChannelTypeOpen || o.Type == ChannelTypePrivate || o.Type == ChannelTypeDirect || o.Type == ChannelTypeGroup) {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if utf8.RuneCountInString(o.Header) > ChannelHeaderMaxRunes {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if utf8.RuneCountInString(o.Purpose) > ChannelPurposeMaxRunes {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
|
}
|
|
|
|
if len(o.CreatorId) > 26 {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.creator_id.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if o.Type != ChannelTypeDirect && o.Type != ChannelTypeGroup {
|
|
userIds := strings.Split(o.Name, "__")
|
|
if ok := gmNameRegex.MatchString(o.Name); ok || (o.Type != ChannelTypeDirect && len(userIds) == 2 && IsValidId(userIds[0]) && IsValidId(userIds[1])) {
|
|
return NewAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "", http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o *Channel) PreSave() {
|
|
if o.Id == "" {
|
|
o.Id = NewId()
|
|
}
|
|
|
|
o.Name = SanitizeUnicode(o.Name)
|
|
o.DisplayName = SanitizeUnicode(o.DisplayName)
|
|
if o.CreateAt == 0 {
|
|
o.CreateAt = GetMillis()
|
|
}
|
|
o.UpdateAt = o.CreateAt
|
|
o.ExtraUpdateAt = 0
|
|
}
|
|
|
|
func (o *Channel) PreUpdate() {
|
|
o.UpdateAt = GetMillis()
|
|
o.Name = SanitizeUnicode(o.Name)
|
|
o.DisplayName = SanitizeUnicode(o.DisplayName)
|
|
}
|
|
|
|
func (o *Channel) IsGroupOrDirect() bool {
|
|
return o.Type == ChannelTypeDirect || o.Type == ChannelTypeGroup
|
|
}
|
|
|
|
func (o *Channel) IsOpen() bool {
|
|
return o.Type == ChannelTypeOpen
|
|
}
|
|
|
|
func (o *Channel) Patch(patch *ChannelPatch) {
|
|
if patch.DisplayName != nil {
|
|
o.DisplayName = *patch.DisplayName
|
|
}
|
|
|
|
if patch.Name != nil {
|
|
o.Name = *patch.Name
|
|
}
|
|
|
|
if patch.Header != nil {
|
|
o.Header = *patch.Header
|
|
}
|
|
|
|
if patch.Purpose != nil {
|
|
o.Purpose = *patch.Purpose
|
|
}
|
|
|
|
if patch.GroupConstrained != nil {
|
|
o.GroupConstrained = patch.GroupConstrained
|
|
}
|
|
}
|
|
|
|
func (o *Channel) MakeNonNil() {
|
|
if o.Props == nil {
|
|
o.Props = make(map[string]any)
|
|
}
|
|
}
|
|
|
|
func (o *Channel) AddProp(key string, value any) {
|
|
o.MakeNonNil()
|
|
|
|
o.Props[key] = value
|
|
}
|
|
|
|
func (o *Channel) IsGroupConstrained() bool {
|
|
return o.GroupConstrained != nil && *o.GroupConstrained
|
|
}
|
|
|
|
func (o *Channel) IsShared() bool {
|
|
return o.Shared != nil && *o.Shared
|
|
}
|
|
|
|
func (o *Channel) GetOtherUserIdForDM(userId string) string {
|
|
user1, user2 := o.GetBothUsersForDM()
|
|
|
|
if user2 == "" {
|
|
return ""
|
|
}
|
|
|
|
if user1 == userId {
|
|
return user2
|
|
}
|
|
|
|
return user1
|
|
}
|
|
|
|
func (o *Channel) GetBothUsersForDM() (string, string) {
|
|
if o.Type != ChannelTypeDirect {
|
|
return "", ""
|
|
}
|
|
|
|
userIds := strings.Split(o.Name, "__")
|
|
if len(userIds) != 2 {
|
|
return "", ""
|
|
}
|
|
|
|
if userIds[0] == userIds[1] {
|
|
return userIds[0], ""
|
|
}
|
|
|
|
return userIds[0], userIds[1]
|
|
}
|
|
|
|
func (o *Channel) Sanitize() Channel {
|
|
return Channel{
|
|
Id: o.Id,
|
|
TeamId: o.TeamId,
|
|
Type: o.Type,
|
|
DisplayName: o.DisplayName,
|
|
}
|
|
}
|
|
|
|
func (t ChannelType) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(string(t))
|
|
}
|
|
|
|
func GetDMNameFromIds(userId1, userId2 string) string {
|
|
if userId1 > userId2 {
|
|
return userId2 + "__" + userId1
|
|
}
|
|
return userId1 + "__" + userId2
|
|
}
|
|
|
|
func GetGroupDisplayNameFromUsers(users []*User, truncate bool) string {
|
|
usernames := make([]string, len(users))
|
|
for index, user := range users {
|
|
usernames[index] = user.Username
|
|
}
|
|
|
|
sort.Strings(usernames)
|
|
|
|
name := strings.Join(usernames, ", ")
|
|
|
|
if truncate && len(name) > ChannelNameMaxLength {
|
|
name = name[:ChannelNameMaxLength]
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
func GetGroupNameFromUserIds(userIds []string) string {
|
|
sort.Strings(userIds)
|
|
|
|
h := sha1.New()
|
|
for _, id := range userIds {
|
|
io.WriteString(h, id)
|
|
}
|
|
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
type GroupMessageConversionRequestBody struct {
|
|
ChannelID string `json:"channel_id"`
|
|
TeamID string `json:"team_id"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
}
|