Files
mostlymatter/server/public/model/channel.go
Miguel de la Cruz b4d8b6239c Excludes remote channels from channel search (#28708)
* 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>
2024-10-18 07:41:24 -06:00

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"`
}