Remote Cluster Service
- provides ability for multiple Mattermost cluster instances to create a trusted connection with each other and exchange messages
- trusted connections are managed via slash commands (for now)
- facilitates features requiring inter-cluster communication, such as Shared Channels
Shared Channels Service
- provides ability to shared channels between one or more Mattermost cluster instances (using trusted connection)
- sharing/unsharing of channels is managed via slash commands (for now)
Этот коммит содержится в:
Doug Lauder
2021-04-01 13:44:56 -04:00
коммит произвёл GitHub
родитель ff980266ac
Коммит 02196e04fa
137 изменённых файлов: 15137 добавлений и 262 удалений

Просмотреть файл

@@ -567,14 +567,20 @@ func (s SqlChannelStore) Save(channel *model.Channel, maxChannelsPerTeam int64)
return newChannel, err
}
func (s SqlChannelStore) CreateDirectChannel(user *model.User, otherUser *model.User) (*model.Channel, error) {
func (s SqlChannelStore) CreateDirectChannel(user *model.User, otherUser *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
channel := new(model.Channel)
for _, option := range channelOptions {
option(channel)
}
channel.DisplayName = ""
channel.Name = model.GetDMNameFromIds(otherUser.Id, user.Id)
channel.Header = ""
channel.Type = model.CHANNEL_DIRECT
channel.Shared = model.NewBool(user.IsRemote() || otherUser.IsRemote())
channel.CreatorId = user.Id
cm1 := &model.ChannelMember{
UserId: user.Id,
@@ -592,13 +598,13 @@ func (s SqlChannelStore) CreateDirectChannel(user *model.User, otherUser *model.
return s.SaveDirectChannel(channel, cm1, cm2)
}
func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) (*model.Channel, error) {
if directchannel.DeleteAt != 0 {
return nil, store.NewErrInvalidInput("Channel", "DeleteAt", directchannel.DeleteAt)
func (s SqlChannelStore) SaveDirectChannel(directChannel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) (*model.Channel, error) {
if directChannel.DeleteAt != 0 {
return nil, store.NewErrInvalidInput("Channel", "DeleteAt", directChannel.DeleteAt)
}
if directchannel.Type != model.CHANNEL_DIRECT {
return nil, store.NewErrInvalidInput("Channel", "Type", directchannel.Type)
if directChannel.Type != model.CHANNEL_DIRECT {
return nil, store.NewErrInvalidInput("Channel", "Type", directChannel.Type)
}
transaction, err := s.GetMaster().Begin()
@@ -607,8 +613,8 @@ func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1
}
defer finalizeTransaction(transaction)
directchannel.TeamId = ""
newChannel, err := s.saveChannelT(transaction, directchannel, 0)
directChannel.TeamId = ""
newChannel, err := s.saveChannelT(transaction, directChannel, 0)
if err != nil {
return newChannel, err
}
@@ -635,7 +641,7 @@ func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1
}
func (s SqlChannelStore) saveChannelT(transaction *gorp.Transaction, channel *model.Channel, maxChannelsPerTeam int64) (*model.Channel, error) {
if channel.Id != "" {
if channel.Id != "" && !channel.IsShared() {
return nil, store.NewErrInvalidInput("Channel", "Id", channel.Id)
}
@@ -3363,3 +3369,53 @@ func (s SqlChannelStore) GroupSyncedChannelCount() (int64, error) {
return count, nil
}
// SetShared sets the Shared flag true/false
func (s SqlChannelStore) SetShared(channelId string, shared bool) error {
squery, args, err := s.getQueryBuilder().
Update("Channels").
Set("Shared", shared).
Where(sq.Eq{"Id": channelId}).
ToSql()
if err != nil {
return errors.Wrap(err, "channel_set_shared_tosql")
}
result, err := s.GetMaster().Exec(squery, args...)
if err != nil {
return errors.Wrap(err, "failed to update `Shared` for Channels")
}
count, err := result.RowsAffected()
if err != nil {
return errors.Wrap(err, "failed to determine rows affected")
}
if count == 0 {
return fmt.Errorf("id not found: %s", channelId)
}
return nil
}
// GetTeamForChannel returns the team for a given channelID.
func (s SqlChannelStore) GetTeamForChannel(channelID string) (*model.Team, error) {
nestedQ, nestedArgs, err := s.getQueryBuilder().Select("TeamId").From("Channels").Where(sq.Eq{"Id": channelID}).ToSql()
if err != nil {
return nil, errors.Wrap(err, "get_team_for_channel_nested_tosql")
}
query, args, err := s.getQueryBuilder().
Select("*").
From("Teams").Where(sq.Expr("Id = ("+nestedQ+")", nestedArgs...)).ToSql()
if err != nil {
return nil, errors.Wrap(err, "get_team_for_channel_tosql")
}
team := model.Team{}
err = s.GetReplica().SelectOne(&team, query, args...)
if err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Team", fmt.Sprintf("channel_id=%s", channelID))
}
return nil, errors.Wrapf(err, "failed to find team with channel_id=%s", channelID)
}
return &team, nil
}

Просмотреть файл

@@ -53,6 +53,7 @@ func newSqlFileInfoStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterfac
"FileInfo.HasPreviewImage",
"FileInfo.MiniPreview",
"Coalesce(FileInfo.Content, '') AS Content",
"Coalesce(FileInfo.RemoteId, '') AS RemoteId",
}
for _, db := range sqlStore.GetAllConns() {
@@ -67,6 +68,7 @@ func newSqlFileInfoStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterfac
table.ColMap("Content").SetMaxSize(0)
table.ColMap("Extension").SetMaxSize(64)
table.ColMap("MimeType").SetMaxSize(256)
table.ColMap("RemoteId").SetMaxSize(26)
}
return s

Просмотреть файл

@@ -42,7 +42,7 @@ func (s *SqlPostStore) ClearCaches() {
}
func postSliceColumns() []string {
return []string{"Id", "CreateAt", "UpdateAt", "EditAt", "DeleteAt", "IsPinned", "UserId", "ChannelId", "RootId", "ParentId", "OriginalId", "Message", "Type", "Props", "Hashtags", "Filenames", "FileIds", "HasReactions"}
return []string{"Id", "CreateAt", "UpdateAt", "EditAt", "DeleteAt", "IsPinned", "UserId", "ChannelId", "RootId", "ParentId", "OriginalId", "Message", "Type", "Props", "Hashtags", "Filenames", "FileIds", "HasReactions", "RemoteId"}
}
func postToSlice(post *model.Post) []interface{} {
@@ -65,6 +65,7 @@ func postToSlice(post *model.Post) []interface{} {
model.ArrayToJson(post.Filenames),
model.ArrayToJson(post.FileIds),
post.HasReactions,
post.RemoteId,
}
}
@@ -89,6 +90,7 @@ func newSqlPostStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) s
table.ColMap("Props").SetMaxSize(8000)
table.ColMap("Filenames").SetMaxSize(model.POST_FILENAMES_MAX_RUNES)
table.ColMap("FileIds").SetMaxSize(300)
table.ColMap("RemoteId").SetMaxSize(26)
}
return s
@@ -117,7 +119,7 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
rootIds := make(map[string]int)
maxDateRootIds := make(map[string]int64)
for idx, post := range posts {
if post.Id != "" {
if post.Id != "" && !post.IsRemote() {
return nil, idx, store.NewErrInvalidInput("Post", "id", post.Id)
}
post.PreSave()
@@ -211,7 +213,7 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
}
}
unknownRepliesPosts := []*model.Post{}
var unknownRepliesPosts []*model.Post
for _, post := range posts {
if post.RootId == "" {
count, ok := rootIds[post.Id]
@@ -521,9 +523,23 @@ func (s *SqlPostStore) Get(ctx context.Context, id string, skipFetchThreads, col
return pl, nil
}
func (s *SqlPostStore) GetSingle(id string) (*model.Post, error) {
func (s *SqlPostStore) GetSingle(id string, inclDeleted bool) (*model.Post, error) {
query := s.getQueryBuilder().
Select("*").
From("Posts").
Where(sq.Eq{"Id": id})
if !inclDeleted {
query = query.Where(sq.Eq{"DeleteAt": 0})
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "getsingleincldeleted_tosql")
}
var post model.Post
err := s.GetReplica().SelectOne(&post, "SELECT * FROM Posts WHERE Id = :Id AND DeleteAt = 0", map[string]interface{}{"Id": id})
err = s.GetReplica().SelectOne(&post, queryString, args...)
if err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Post", id)
@@ -869,6 +885,11 @@ func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFr
var posts []*model.Post
order := "DESC"
if options.SortAscending {
order = "ASC"
}
replyCountQuery1 := ""
replyCountQuery2 := ""
if options.SkipFetchThreads {
@@ -905,7 +926,7 @@ func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFr
AND ChannelId = :ChannelId
LIMIT 1000) temp_tab))
) j ON p1.Id = j.Id
ORDER BY CreateAt DESC`
ORDER BY CreateAt ` + order
} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
query = `WITH cte AS (SELECT
*
@@ -917,7 +938,7 @@ func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFr
(SELECT *` + replyCountQuery2 + ` FROM cte)
UNION
(SELECT *` + replyCountQuery1 + ` FROM Posts p1 WHERE id in (SELECT rootid FROM cte))
ORDER BY CreateAt DESC`
ORDER BY CreateAt ` + order
}
_, err := s.GetReplica().Select(&posts, query, map[string]interface{}{"ChannelId": options.ChannelId, "Time": options.Time})
@@ -937,6 +958,56 @@ func (s *SqlPostStore) GetPostsSince(options model.GetPostsSinceOptions, allowFr
return list, nil
}
func (s *SqlPostStore) GetPostsSinceForSync(options model.GetPostsSinceForSyncOptions, _ /* allowFromCache */ bool) ([]*model.Post, error) {
if options.Limit < 0 || options.Limit > 1000 {
return nil, store.NewErrInvalidInput("Post", "<options.Limit>", options.Limit)
}
order := " ASC"
if options.SortDescending {
order = " DESC"
}
query := s.getQueryBuilder().
Select("*").
From("Posts").
Where(sq.GtOrEq{"UpdateAt": options.Since}).
Where(sq.Eq{"ChannelId": options.ChannelId}).
Limit(uint64(options.Limit)).
OrderBy("CreateAt"+order, "DeleteAt", "Id")
if options.Until > 0 {
query = query.Where(sq.LtOrEq{"UpdateAt": options.Until})
}
if !options.IncludeDeleted {
query = query.Where(sq.Eq{"DeleteAt": 0})
}
if options.ExcludeRemoteId != "" {
query = query.Where(sq.NotEq{"COALESCE(Posts.RemoteId,'')": options.ExcludeRemoteId})
}
if options.Offset > 0 {
query = query.Offset(uint64(options.Offset))
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "getpostssinceforsync_tosql")
}
var posts []*model.Post
_, err = s.GetReplica().Select(&posts, queryString, args...)
if err != nil {
return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", options.ChannelId)
}
return posts, nil
}
func (s *SqlPostStore) GetPostsBefore(options model.GetPostsOptions) (*model.PostList, error) {
return s.getPostsAround(true, options)
}

Просмотреть файл

@@ -4,6 +4,8 @@
package sqlstore
import (
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/store"
@@ -24,6 +26,7 @@ func newSqlReactionStore(sqlStore *SqlStore) store.ReactionStore {
table.ColMap("UserId").SetMaxSize(26)
table.ColMap("PostId").SetMaxSize(26)
table.ColMap("EmojiName").SetMaxSize(64)
table.ColMap("RemoteId").SetMaxSize(26)
}
return s
@@ -75,26 +78,56 @@ func (s *SqlReactionStore) Delete(reaction *model.Reaction) (*model.Reaction, er
return reaction, nil
}
// GetForPost returns all reactions associated with `postId` that are not deleted.
func (s *SqlReactionStore) GetForPost(postId string, allowFromCache bool) ([]*model.Reaction, error) {
var reactions []*model.Reaction
queryString, args, err := s.getQueryBuilder().
Select("UserId", "PostId", "EmojiName", "CreateAt", "COALESCE(UpdateAt, CreateAt) As UpdateAt",
"COALESCE(DeleteAt, 0) As DeleteAt", "RemoteId").
From("Reactions").
Where(sq.Eq{"PostId": postId}).
Where(sq.Eq{"COALESCE(DeleteAt, 0)": 0}).
OrderBy("CreateAt").
ToSql()
if _, err := s.GetReplica().Select(&reactions,
`SELECT
UserId,
PostId,
EmojiName,
CreateAt,
COALESCE(UpdateAt, CreateAt) As UpdateAt,
COALESCE(DeleteAt, 0) As DeleteAt
FROM
Reactions
WHERE
PostId = :PostId AND COALESCE(DeleteAt, 0) = 0
ORDER BY
CreateAt`, map[string]interface{}{"PostId": postId}); err != nil {
return nil, errors.Wrapf(err, "failed to get Reactions with postId=%s", postId)
if err != nil {
return nil, errors.Wrap(err, "reactions_getforpost_tosql")
}
var reactions []*model.Reaction
if _, err := s.GetReplica().Select(&reactions, queryString, args...); err != nil {
return nil, errors.Wrapf(err, "failed to get Reactions with postId=%s", postId)
}
return reactions, nil
}
// GetForPostSince returns all reactions associated with `postId` updated after `since`.
func (s *SqlReactionStore) GetForPostSince(postId string, since int64, excludeRemoteId string, inclDeleted bool) ([]*model.Reaction, error) {
query := s.getQueryBuilder().
Select("UserId", "PostId", "EmojiName", "CreateAt", "COALESCE(UpdateAt, CreateAt) As UpdateAt",
"COALESCE(DeleteAt, 0) As DeleteAt", "RemoteId").
From("Reactions").
Where(sq.Eq{"PostId": postId}).
Where(sq.Gt{"UpdateAt": since})
if excludeRemoteId != "" {
query = query.Where(sq.NotEq{"COALESCE(RemoteId, '')": excludeRemoteId})
}
if !inclDeleted {
query = query.Where(sq.Eq{"COALESCE(DeleteAt, 0)": 0})
}
query.OrderBy("CreateAt")
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "reactions_getforpostsince_tosql")
}
var reactions []*model.Reaction
if _, err := s.GetReplica().Select(&reactions, queryString, args...); err != nil {
return nil, errors.Wrapf(err, "failed to find reactions")
}
return reactions, nil
}
@@ -109,7 +142,8 @@ func (s *SqlReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction,
EmojiName,
CreateAt,
COALESCE(UpdateAt, CreateAt) As UpdateAt,
COALESCE(DeleteAt, 0) As DeleteAt
COALESCE(DeleteAt, 0) As DeleteAt,
RemoteId
FROM
Reactions
WHERE
@@ -133,16 +167,17 @@ func (s *SqlReactionStore) DeleteAllWithEmojiName(emojiName string) error {
if _, err := s.GetReplica().Select(&reactions,
`SELECT
UserId,
PostId,
EmojiName,
CreateAt,
COALESCE(UpdateAt, CreateAt) As UpdateAt,
COALESCE(DeleteAt, 0) As DeleteAt
FROM
Reactions
WHERE
EmojiName = :EmojiName AND COALESCE(DeleteAt, 0) = 0`, params); err != nil {
UserId,
PostId,
EmojiName,
CreateAt,
COALESCE(UpdateAt, CreateAt) As UpdateAt,
COALESCE(DeleteAt, 0) As DeleteAt,
RemoteId
FROM
Reactions
WHERE
EmojiName = :EmojiName AND COALESCE(DeleteAt, 0) = 0`, params); err != nil {
return errors.Wrapf(err, "failed to get Reactions with emojiName=%s", emojiName)
}
@@ -201,28 +236,29 @@ func (s *SqlReactionStore) saveReactionAndUpdatePost(transaction *gorp.Transacti
"EmojiName": reaction.EmojiName,
"CreateAt": reaction.CreateAt,
"UpdateAt": reaction.UpdateAt,
"RemoteId": reaction.RemoteId,
}
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
if _, err := transaction.Exec(
`INSERT INTO
Reactions
(UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt)
(UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt, RemoteId)
VALUES
(:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0)
(:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0, :RemoteId)
ON DUPLICATE KEY UPDATE
UpdateAt = :UpdateAt, DeleteAt = 0`, params); err != nil {
UpdateAt = :UpdateAt, DeleteAt = 0, RemoteId = :RemoteId`, params); err != nil {
return err
}
} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
if _, err := transaction.Exec(
`INSERT INTO
Reactions
(UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt)
(UserId, PostId, EmojiName, CreateAt, UpdateAt, DeleteAt, RemoteId)
VALUES
(:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0)
(:UserId, :PostId, :EmojiName, :CreateAt, :UpdateAt, 0, :RemoteId)
ON CONFLICT (UserId, PostId, EmojiName)
DO UPDATE SET UpdateAt = :UpdateAt, DeleteAt = 0`, params); err != nil {
DO UPDATE SET UpdateAt = :UpdateAt, DeleteAt = 0, RemoteId = :RemoteId`, params); err != nil {
return err
}
}
@@ -237,13 +273,14 @@ func deleteReactionAndUpdatePost(transaction *gorp.Transaction, reaction *model.
"CreateAt": reaction.CreateAt,
"UpdateAt": reaction.UpdateAt,
"DeleteAt": reaction.UpdateAt, // DeleteAt = UpdateAt
"RemoteId": reaction.RemoteId,
}
if _, err := transaction.Exec(
`UPDATE
Reactions
SET
UpdateAt = :UpdateAt, DeleteAt = :DeleteAt
UpdateAt = :UpdateAt, DeleteAt = :DeleteAt, RemoteId = :RemoteId
WHERE
PostId = :PostId AND
UserId = :UserId AND

186
store/sqlstore/remote_cluster_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,186 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"fmt"
"strings"
sq "github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store"
)
type sqlRemoteClusterStore struct {
*SqlStore
}
func newSqlRemoteClusterStore(sqlStore *SqlStore) store.RemoteClusterStore {
s := &sqlRemoteClusterStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.RemoteCluster{}, "RemoteClusters").SetKeys(false, "RemoteId")
table.ColMap("RemoteId").SetMaxSize(26)
table.ColMap("RemoteTeamId").SetMaxSize(26)
table.ColMap("DisplayName").SetMaxSize(64)
table.ColMap("SiteURL").SetMaxSize(512)
table.ColMap("Token").SetMaxSize(26)
table.ColMap("RemoteToken").SetMaxSize(26)
table.ColMap("Topics").SetMaxSize(512)
table.ColMap("CreatorId").SetMaxSize(26)
}
return s
}
func (s sqlRemoteClusterStore) Save(remoteCluster *model.RemoteCluster) (*model.RemoteCluster, error) {
remoteCluster.PreSave()
if err := remoteCluster.IsValid(); err != nil {
return nil, err
}
if err := s.GetMaster().Insert(remoteCluster); err != nil {
return nil, errors.Wrap(err, "failed to save RemoteCluster")
}
return remoteCluster, nil
}
func (s sqlRemoteClusterStore) Update(remoteCluster *model.RemoteCluster) (*model.RemoteCluster, error) {
remoteCluster.PreUpdate()
if err := remoteCluster.IsValid(); err != nil {
return nil, err
}
if _, err := s.GetMaster().Update(remoteCluster); err != nil {
return nil, errors.Wrap(err, "failed to update RemoteCluster")
}
return remoteCluster, nil
}
func (s sqlRemoteClusterStore) Delete(remoteId string) (bool, error) {
squery, args, err := s.getQueryBuilder().
Delete("RemoteClusters").
Where(sq.Eq{"RemoteId": remoteId}).
ToSql()
if err != nil {
return false, errors.Wrap(err, "delete_remote_cluster_tosql")
}
result, err := s.GetMaster().Exec(squery, args...)
if err != nil {
return false, errors.Wrap(err, "failed to delete RemoteCluster")
}
count, err := result.RowsAffected()
if err != nil {
return false, errors.Wrap(err, "failed to determine rows affected")
}
return count > 0, nil
}
func (s sqlRemoteClusterStore) Get(remoteId string) (*model.RemoteCluster, error) {
query := s.getQueryBuilder().
Select("*").
From("RemoteClusters").
Where(sq.Eq{"RemoteId": remoteId})
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "remote_cluster_get_tosql")
}
var rc model.RemoteCluster
if err := s.GetReplica().SelectOne(&rc, queryString, args...); err != nil {
return nil, errors.Wrapf(err, "failed to find RemoteCluster")
}
return &rc, nil
}
func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
query := s.getQueryBuilder().
Select("rc.*").
From("RemoteClusters rc")
if filter.InChannel != "" {
query = query.Where("rc.RemoteId IN (SELECT scr.RemoteId FROM SharedChannelRemotes scr WHERE scr.ChannelId = ?)", filter.InChannel)
}
if filter.NotInChannel != "" {
query = query.Where("rc.RemoteId NOT IN (SELECT scr.RemoteId FROM SharedChannelRemotes scr WHERE scr.ChannelId = ?)", filter.NotInChannel)
}
if filter.ExcludeOffline {
query = query.Where(sq.Gt{"rc.LastPingAt": model.GetMillis() - model.RemoteOfflineAfterMillis})
}
if filter.CreatorId != "" {
query = query.Where(sq.Eq{"rc.CreatorId": filter.CreatorId})
}
if filter.OnlyConfirmed {
query = query.Where(sq.NotEq{"rc.SiteURL": ""})
}
if filter.Topic != "" {
trimmed := strings.TrimSpace(filter.Topic)
if trimmed == "" || trimmed == "*" {
return nil, errors.New("invalid topic")
}
queryTopic := fmt.Sprintf("%% %s %%", trimmed)
query = query.Where(sq.Or{sq.Like{"rc.Topics": queryTopic}, sq.Eq{"rc.Topics": "*"}})
}
queryString, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "remote_cluster_getall_tosql")
}
var list []*model.RemoteCluster
if _, err := s.GetReplica().Select(&list, queryString, args...); err != nil {
return nil, errors.Wrapf(err, "failed to find RemoteClusters")
}
return list, nil
}
func (s sqlRemoteClusterStore) UpdateTopics(remoteClusterid string, topics string) (*model.RemoteCluster, error) {
rc, err := s.Get(remoteClusterid)
if err != nil {
return nil, err
}
rc.Topics = topics
rc.PreUpdate()
if _, err = s.GetMaster().Update(rc); err != nil {
return nil, err
}
return rc, nil
}
func (s sqlRemoteClusterStore) SetLastPingAt(remoteClusterId string) error {
query := s.getQueryBuilder().
Update("RemoteClusters").
Set("LastPingAt", model.GetMillis()).
Where(sq.Eq{"RemoteId": remoteClusterId})
queryString, args, err := query.ToSql()
if err != nil {
return errors.Wrap(err, "remote_cluster_tosql")
}
if _, err := s.GetMaster().Exec(queryString, args...); err != nil {
return errors.Wrap(err, "failed to update RemoteCluster")
}
return nil
}
func (s *sqlRemoteClusterStore) createIndexesIfNotExists() {
uniquenessColumns := []string{"SiteUrl", "RemoteTeamId"}
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
uniquenessColumns = []string{"RemoteTeamId", "SiteUrl(168)"}
}
s.CreateUniqueCompositeIndexIfNotExists(RemoteClusterSiteURLUniqueIndex, "RemoteClusters", uniquenessColumns)
}

14
store/sqlstore/remote_cluster_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost-server/v5/store/storetest"
)
func TestRemoteClusterStore(t *testing.T) {
StoreTest(t, storetest.TestRemoteClusterStore)
}

712
store/sqlstore/shared_channel_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,712 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"fmt"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store"
sq "github.com/Masterminds/squirrel"
"github.com/pkg/errors"
)
type SqlSharedChannelStore struct {
*SqlStore
}
func newSqlSharedChannelStore(sqlStore *SqlStore) store.SharedChannelStore {
s := &SqlSharedChannelStore{
SqlStore: sqlStore,
}
for _, db := range sqlStore.GetAllConns() {
tableSharedChannels := db.AddTableWithName(model.SharedChannel{}, "SharedChannels").SetKeys(false, "ChannelId")
tableSharedChannels.ColMap("ChannelId").SetMaxSize(26)
tableSharedChannels.ColMap("TeamId").SetMaxSize(26)
tableSharedChannels.ColMap("CreatorId").SetMaxSize(26)
tableSharedChannels.ColMap("ShareName").SetMaxSize(64)
tableSharedChannels.SetUniqueTogether("ShareName", "TeamId")
tableSharedChannels.ColMap("ShareDisplayName").SetMaxSize(64)
tableSharedChannels.ColMap("SharePurpose").SetMaxSize(250)
tableSharedChannels.ColMap("ShareHeader").SetMaxSize(1024)
tableSharedChannels.ColMap("RemoteId").SetMaxSize(26)
tableSharedChannelRemotes := db.AddTableWithName(model.SharedChannelRemote{}, "SharedChannelRemotes").SetKeys(false, "Id", "ChannelId")
tableSharedChannelRemotes.ColMap("Id").SetMaxSize(26)
tableSharedChannelRemotes.ColMap("ChannelId").SetMaxSize(26)
tableSharedChannelRemotes.ColMap("Description").SetMaxSize(64)
tableSharedChannelRemotes.ColMap("CreatorId").SetMaxSize(26)
tableSharedChannelRemotes.ColMap("RemoteId").SetMaxSize(26)
tableSharedChannelRemotes.SetUniqueTogether("ChannelId", "RemoteId")
tableSharedChannelUsers := db.AddTableWithName(model.SharedChannelUser{}, "SharedChannelUsers").SetKeys(false, "Id")
tableSharedChannelUsers.ColMap("Id").SetMaxSize(26)
tableSharedChannelUsers.ColMap("UserId").SetMaxSize(26)
tableSharedChannelUsers.ColMap("RemoteId").SetMaxSize(26)
tableSharedChannelUsers.SetUniqueTogether("UserId", "RemoteId")
tableSharedChannelFiles := db.AddTableWithName(model.SharedChannelAttachment{}, "SharedChannelAttachments").SetKeys(false, "Id")
tableSharedChannelFiles.ColMap("Id").SetMaxSize(26)
tableSharedChannelFiles.ColMap("FileId").SetMaxSize(26)
tableSharedChannelFiles.ColMap("RemoteId").SetMaxSize(26)
tableSharedChannelFiles.SetUniqueTogether("FileId", "RemoteId")
}
return s
}
func (s SqlSharedChannelStore) createIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_sharedchannelusers_user_id", "SharedChannelUsers", "UserId")
s.CreateIndexIfNotExists("idx_sharedchannelusers_remote_id", "SharedChannelUsers", "RemoteId")
}
// Save inserts a new shared channel record.
func (s SqlSharedChannelStore) Save(sc *model.SharedChannel) (*model.SharedChannel, error) {
sc.PreSave()
if err := sc.IsValid(); err != nil {
return nil, err
}
// make sure the shared channel is associated with a real channel.
channel, err := s.stores.channel.Get(sc.ChannelId, true)
if err != nil {
return nil, fmt.Errorf("invalid channel: %w", err)
}
transaction, err := s.GetMaster().Begin()
if err != nil {
return nil, errors.Wrap(err, "begin_transaction")
}
defer finalizeTransaction(transaction)
if err := transaction.Insert(sc); err != nil {
return nil, errors.Wrapf(err, "save_shared_channel: ChannelId=%s", sc.ChannelId)
}
// set `Shared` flag in Channels table if needed
if channel.Shared == nil || !*channel.Shared {
if err := s.stores.channel.SetShared(channel.Id, true); err != nil {
return nil, err
}
}
if err := transaction.Commit(); err != nil {
return nil, errors.Wrap(err, "commit_transaction")
}
return sc, nil
}
// Get fetches a shared channel by channel_id.
func (s SqlSharedChannelStore) Get(channelId string) (*model.SharedChannel, error) {
var sc model.SharedChannel
query := s.getQueryBuilder().
Select("*").
From("SharedChannels").
Where(sq.Eq{"SharedChannels.ChannelId": channelId})
squery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "getsharedchannel_tosql")
}
if err := s.GetReplica().SelectOne(&sc, squery, args...); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("SharedChannel", channelId)
}
return nil, errors.Wrapf(err, "failed to find shared channel with ChannelId=%s", channelId)
}
return &sc, nil
}
// HasChannel returns whether a given channelID is a shared channel or not.
func (s SqlSharedChannelStore) HasChannel(channelID string) (bool, error) {
builder := s.getQueryBuilder().
Select("1").
Prefix("SELECT EXISTS (").
From("SharedChannels").
Where(sq.Eq{"SharedChannels.ChannelId": channelID}).
Suffix(")")
query, args, err := builder.ToSql()
if err != nil {
return false, errors.Wrapf(err, "get_shared_channel_exists_tosql")
}
var exists bool
if err := s.GetReplica().SelectOne(&exists, query, args...); err != nil {
return exists, errors.Wrapf(err, "failed to get shared channel for channel_id=%s", channelID)
}
return exists, nil
}
// GetAll fetches a paginated list of shared channels filtered by SharedChannelSearchOpts.
func (s SqlSharedChannelStore) GetAll(offset, limit int, opts model.SharedChannelFilterOpts) ([]*model.SharedChannel, error) {
if opts.ExcludeHome && opts.ExcludeRemote {
return nil, errors.New("cannot exclude home and remote shared channels")
}
safeConv := func(offset, limit int) (uint64, uint64, error) {
if offset < 0 {
return 0, 0, errors.New("offset must be positive integer")
}
if limit < 0 {
return 0, 0, errors.New("limit must be positive integer")
}
return uint64(offset), uint64(limit), nil
}
safeOffset, safeLimit, err := safeConv(offset, limit)
if err != nil {
return nil, err
}
query := s.getSharedChannelsQuery(opts, false)
query = query.OrderBy("sc.ShareDisplayName, sc.ShareName").Limit(safeLimit).Offset(safeOffset)
squery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "failed to create query")
}
var channels []*model.SharedChannel
_, err = s.GetReplica().Select(&channels, squery, args...)
if err != nil {
return nil, errors.Wrap(err, "failed to get shared channels")
}
return channels, nil
}
// GetAllCount returns the number of shared channels that would be fetched using SharedChannelSearchOpts.
func (s SqlSharedChannelStore) GetAllCount(opts model.SharedChannelFilterOpts) (int64, error) {
if opts.ExcludeHome && opts.ExcludeRemote {
return 0, errors.New("cannot exclude home and remote shared channels")
}
query := s.getSharedChannelsQuery(opts, true)
squery, args, err := query.ToSql()
if err != nil {
return 0, errors.Wrap(err, "failed to create query")
}
count, err := s.GetReplica().SelectInt(squery, args...)
if err != nil {
return 0, errors.Wrap(err, "failed to count channels")
}
return count, nil
}
func (s SqlSharedChannelStore) getSharedChannelsQuery(opts model.SharedChannelFilterOpts, forCount bool) sq.SelectBuilder {
var selectStr string
if forCount {
selectStr = "count(sc.ChannelId)"
} else {
selectStr = "sc.*"
}
query := s.getQueryBuilder().
Select(selectStr).
From("SharedChannels AS sc")
if opts.TeamId != "" {
query = query.Where(sq.Eq{"sc.TeamId": opts.TeamId})
}
if opts.CreatorId != "" {
query = query.Where(sq.Eq{"sc.CreatorId": opts.CreatorId})
}
if opts.ExcludeHome {
query = query.Where(sq.NotEq{"sc.Home": true})
}
if opts.ExcludeRemote {
query = query.Where(sq.Eq{"sc.Home": true})
}
return query
}
// Update updates the shared channel.
func (s SqlSharedChannelStore) Update(sc *model.SharedChannel) (*model.SharedChannel, error) {
if err := sc.IsValid(); err != nil {
return nil, err
}
count, err := s.GetMaster().Update(sc)
if err != nil {
return nil, errors.Wrapf(err, "failed to update shared channel with channelId=%s", sc.ChannelId)
}
if count != 1 {
return nil, fmt.Errorf("expected number of shared channels to be updated is 1 but was %d", count)
}
return sc, nil
}
// Delete deletes a single shared channel plus associated SharedChannelRemotes.
// Returns true if shared channel found and deleted, false if not found.
func (s SqlSharedChannelStore) Delete(channelId string) (bool, error) {
transaction, err := s.GetMaster().Begin()
if err != nil {
return false, errors.Wrap(err, "DeleteSharedChannel: begin_transaction")
}
defer finalizeTransaction(transaction)
squery, args, err := s.getQueryBuilder().
Delete("SharedChannels").
Where(sq.Eq{"SharedChannels.ChannelId": channelId}).
ToSql()
if err != nil {
return false, errors.Wrap(err, "delete_shared_channel_tosql")
}
result, err := transaction.Exec(squery, args...)
if err != nil {
return false, errors.Wrap(err, "failed to delete SharedChannel")
}
// Also remove remotes from SharedChannelRemotes (if any).
squery, args, err = s.getQueryBuilder().
Delete("SharedChannelRemotes").
Where(sq.Eq{"ChannelId": channelId}).
ToSql()
if err != nil {
return false, errors.Wrap(err, "delete_shared_channel_remotes_tosql")
}
_, err = transaction.Exec(squery, args...)
if err != nil {
return false, errors.Wrap(err, "failed to delete SharedChannelRemotes")
}
count, err := result.RowsAffected()
if err != nil {
return false, errors.Wrap(err, "failed to determine rows affected")
}
if count > 0 {
// unset the channel's Shared flag
if err = s.Channel().SetShared(channelId, false); err != nil {
return false, errors.Wrap(err, "error unsetting channel share flag")
}
}
if err = transaction.Commit(); err != nil {
return false, errors.Wrap(err, "commit_transaction")
}
return count > 0, nil
}
// SaveRemote inserts a new shared channel remote record.
func (s SqlSharedChannelStore) SaveRemote(remote *model.SharedChannelRemote) (*model.SharedChannelRemote, error) {
remote.PreSave()
if err := remote.IsValid(); err != nil {
return nil, err
}
// make sure the shared channel remote is associated with a real channel.
if _, err := s.stores.channel.Get(remote.ChannelId, true); err != nil {
return nil, fmt.Errorf("invalid channel: %w", err)
}
if err := s.GetMaster().Insert(remote); err != nil {
return nil, errors.Wrapf(err, "save_shared_channel_remote: channel_id=%s, id=%s", remote.ChannelId, remote.Id)
}
return remote, nil
}
// Update updates the shared channel remote.
func (s SqlSharedChannelStore) UpdateRemote(remote *model.SharedChannelRemote) (*model.SharedChannelRemote, error) {
if err := remote.IsValid(); err != nil {
return nil, err
}
count, err := s.GetMaster().Update(remote)
if err != nil {
return nil, errors.Wrapf(err, "failed to update shared channel remote with remoteId=%s", remote.Id)
}
if count != 1 {
return nil, fmt.Errorf("expected number of shared channel remotes to be updated is 1 but was %d", count)
}
return remote, nil
}
// GetRemote fetches a shared channel remote by id.
func (s SqlSharedChannelStore) GetRemote(id string) (*model.SharedChannelRemote, error) {
var remote model.SharedChannelRemote
query := s.getQueryBuilder().
Select("*").
From("SharedChannelRemotes").
Where(sq.Eq{"SharedChannelRemotes.Id": id})
squery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "get_shared_channel_remote_tosql")
}
if err := s.GetReplica().SelectOne(&remote, squery, args...); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("SharedChannelRemote", id)
}
return nil, errors.Wrapf(err, "failed to find shared channel remote with id=%s", id)
}
return &remote, nil
}
// GetRemoteByIds fetches a shared channel remote by channel id and remote cluster id.
func (s SqlSharedChannelStore) GetRemoteByIds(channelId string, remoteId string) (*model.SharedChannelRemote, error) {
var remote model.SharedChannelRemote
query := s.getQueryBuilder().
Select("*").
From("SharedChannelRemotes").
Where(sq.Eq{"SharedChannelRemotes.ChannelId": channelId}).
Where(sq.Eq{"SharedChannelRemotes.RemoteId": remoteId})
squery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "get_shared_channel_remote_by_ids_tosql")
}
if err := s.GetReplica().SelectOne(&remote, squery, args...); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("SharedChannelRemote", fmt.Sprintf("channelId=%s, remoteId=%s", channelId, remoteId))
}
return nil, errors.Wrapf(err, "failed to find shared channel remote with channelId=%s, remoteId=%s", channelId, remoteId)
}
return &remote, nil
}
// GetRemotes fetches all shared channel remotes associated with channel_id.
func (s SqlSharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
var remotes []*model.SharedChannelRemote
query := s.getQueryBuilder().
Select("*").
From("SharedChannelRemotes")
if opts.ChannelId != "" {
query = query.Where(sq.Eq{"ChannelId": opts.ChannelId})
}
if opts.RemoteId != "" {
query = query.Where(sq.Eq{"RemoteId": opts.RemoteId})
}
if !opts.InclUnconfirmed {
query = query.Where(sq.Eq{"IsInviteConfirmed": true})
}
squery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "get_shared_channel_remotes_tosql")
}
if _, err := s.GetReplica().Select(&remotes, squery, args...); err != nil {
if err != sql.ErrNoRows {
return nil, errors.Wrapf(err, "failed to get shared channel remotes for channel_id=%s; remote_id=%s",
opts.ChannelId, opts.RemoteId)
}
}
return remotes, nil
}
// HasRemote returns whether a given remoteId and channelId are present in the shared channel remotes or not.
func (s SqlSharedChannelStore) HasRemote(channelID string, remoteId string) (bool, error) {
builder := s.getQueryBuilder().
Select("1").
Prefix("SELECT EXISTS (").
From("SharedChannelRemotes").
Where(sq.Eq{"RemoteId": remoteId}).
Where(sq.Eq{"ChannelId": channelID}).
Suffix(")")
query, args, err := builder.ToSql()
if err != nil {
return false, errors.Wrapf(err, "get_shared_channel_hasremote_tosql")
}
var hasRemote bool
if err := s.GetReplica().SelectOne(&hasRemote, query, args...); err != nil {
return hasRemote, errors.Wrapf(err, "failed to get channel remotes for channel_id=%s", channelID)
}
return hasRemote, nil
}
// GetRemoteForUser returns a remote cluster for the given userId only if the user belongs to at least one channel
// shared with the remote.
func (s SqlSharedChannelStore) GetRemoteForUser(remoteId string, userId string) (*model.RemoteCluster, error) {
builder := s.getQueryBuilder().
Select("rc.*").
From("RemoteClusters AS rc").
Join("SharedChannelRemotes AS scr ON rc.RemoteId = scr.RemoteId").
Join("ChannelMembers AS cm ON scr.ChannelId = cm.ChannelId").
Where(sq.Eq{"rc.RemoteId": remoteId}).
Where(sq.Eq{"cm.UserId": userId})
query, args, err := builder.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "get_remote_for_user_tosql")
}
var rc model.RemoteCluster
if err := s.GetReplica().SelectOne(&rc, query, args...); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("RemoteCluster", remoteId)
}
return nil, errors.Wrapf(err, "failed to get remote for user_id=%s", userId)
}
return &rc, nil
}
// UpdateRemoteNextSyncAt updates the NextSyncAt timestamp for the specified SharedChannelRemote.
func (s SqlSharedChannelStore) UpdateRemoteNextSyncAt(id string, syncTime int64) error {
squery, args, err := s.getQueryBuilder().
Update("SharedChannelRemotes").
Set("NextSyncAt", syncTime).
Where(sq.Eq{"Id": id}).
ToSql()
if err != nil {
return errors.Wrap(err, "update_shared_channel_remote_next_sync_at_tosql")
}
result, err := s.GetMaster().Exec(squery, args...)
if err != nil {
return errors.Wrap(err, "failed to update NextSyncAt for SharedChannelRemote")
}
count, err := result.RowsAffected()
if err != nil {
return errors.Wrap(err, "failed to determine rows affected")
}
if count == 0 {
return fmt.Errorf("id not found: %s", id)
}
return nil
}
// DeleteRemote deletes a single shared channel remote.
// Returns true if remote found and deleted, false if not found.
func (s SqlSharedChannelStore) DeleteRemote(id string) (bool, error) {
squery, args, err := s.getQueryBuilder().
Delete("SharedChannelRemotes").
Where(sq.Eq{"Id": id}).
ToSql()
if err != nil {
return false, errors.Wrap(err, "delete_shared_channel_remote_tosql")
}
result, err := s.GetMaster().Exec(squery, args...)
if err != nil {
return false, errors.Wrap(err, "failed to delete SharedChannelRemote")
}
count, err := result.RowsAffected()
if err != nil {
return false, errors.Wrap(err, "failed to determine rows affected")
}
return count > 0, nil
}
// GetRemotesStatus returns the status for each remote invited to the
// specified shared channel.
func (s SqlSharedChannelStore) GetRemotesStatus(channelId string) ([]*model.SharedChannelRemoteStatus, error) {
var status []*model.SharedChannelRemoteStatus
query := s.getQueryBuilder().
Select("scr.ChannelId, rc.DisplayName, rc.SiteURL, rc.LastPingAt, scr.NextSyncAt, scr.Description, sc.ReadOnly, scr.IsInviteAccepted").
From("SharedChannelRemotes scr, RemoteClusters rc, SharedChannels sc").
Where("scr.RemoteId = rc.RemoteId").
Where("scr.ChannelId = sc.ChannelId").
Where(sq.Eq{"scr.ChannelId": channelId})
squery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "get_shared_channel_remotes_status_tosql")
}
if _, err := s.GetReplica().Select(&status, squery, args...); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("SharedChannelRemoteStatus", channelId)
}
return nil, errors.Wrapf(err, "failed to get shared channel remote status for channel_id=%s", channelId)
}
return status, nil
}
// SaveUser inserts a new shared channel user record to the SharedChannelUsers table.
func (s SqlSharedChannelStore) SaveUser(scUser *model.SharedChannelUser) (*model.SharedChannelUser, error) {
scUser.PreSave()
if err := scUser.IsValid(); err != nil {
return nil, err
}
if err := s.GetMaster().Insert(scUser); err != nil {
return nil, errors.Wrapf(err, "save_shared_channel_user: user_id=%s, remote_id=%s", scUser.UserId, scUser.RemoteId)
}
return scUser, nil
}
// GetUser fetches a shared channel user based on user_id and remoteId.
func (s SqlSharedChannelStore) GetUser(userId string, remoteId string) (*model.SharedChannelUser, error) {
var scu model.SharedChannelUser
squery, args, err := s.getQueryBuilder().
Select("*").
From("SharedChannelUsers").
Where(sq.Eq{"SharedChannelUsers.UserId": userId}).
Where(sq.Eq{"SharedChannelUsers.RemoteId": remoteId}).
ToSql()
if err != nil {
return nil, errors.Wrapf(err, "getsharedchanneluser_tosql")
}
if err := s.GetReplica().SelectOne(&scu, squery, args...); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("SharedChannelUser", userId)
}
return nil, errors.Wrapf(err, "failed to find shared channel user with UserId=%s, RemoteId=%s", userId, remoteId)
}
return &scu, nil
}
// UpdateUserLastSyncAt updates the LastSyncAt timestamp for the specified SharedChannelUser.
func (s SqlSharedChannelStore) UpdateUserLastSyncAt(id string, syncTime int64) error {
squery, args, err := s.getQueryBuilder().
Update("SharedChannelUsers").
Set("LastSyncAt", syncTime).
Where(sq.Eq{"Id": id}).
ToSql()
if err != nil {
return errors.Wrap(err, "update_shared_channel_user_last_sync_at_tosql")
}
result, err := s.GetMaster().Exec(squery, args...)
if err != nil {
return errors.Wrap(err, "failed to update LastSycnAt for SharedChannelUser")
}
count, err := result.RowsAffected()
if err != nil {
return errors.Wrap(err, "failed to determine rows affected")
}
if count == 0 {
return fmt.Errorf("id not found: %s", id)
}
return nil
}
// SaveAttachment inserts a new shared channel file attachment record to the SharedChannelFiles table.
func (s SqlSharedChannelStore) SaveAttachment(attachment *model.SharedChannelAttachment) (*model.SharedChannelAttachment, error) {
attachment.PreSave()
if err := attachment.IsValid(); err != nil {
return nil, err
}
if err := s.GetMaster().Insert(attachment); err != nil {
return nil, errors.Wrapf(err, "save_shared_channel_attachment: file_id=%s, remote_id=%s", attachment.FileId, attachment.RemoteId)
}
return attachment, nil
}
// UpsertAttachment inserts a new shared channel file attachment record to the SharedChannelFiles table or updates its
// LastSyncAt.
func (s SqlSharedChannelStore) UpsertAttachment(attachment *model.SharedChannelAttachment) (string, error) {
attachment.PreSave()
if err := attachment.IsValid(); err != nil {
return "", err
}
params := map[string]interface{}{
"Id": attachment.Id,
"FileId": attachment.FileId,
"RemoteId": attachment.RemoteId,
"CreateAt": attachment.CreateAt,
"LastSyncAt": attachment.LastSyncAt,
}
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
if _, err := s.GetMaster().Exec(
`INSERT INTO
SharedChannelAttachments
(Id, FileId, RemoteId, CreateAt, LastSyncAt)
VALUES
(:Id, :FileId, :RemoteId, :CreateAt, :LastSyncAt)
ON DUPLICATE KEY UPDATE
LastSyncAt = :LastSyncAt`, params); err != nil {
return "", err
}
} else if s.DriverName() == model.DATABASE_DRIVER_POSTGRES {
if _, err := s.GetMaster().Exec(
`INSERT INTO
SharedChannelAttachments
(Id, FileId, RemoteId, CreateAt, LastSyncAt)
VALUES
(:Id, :FileId, :RemoteId, :CreateAt, :LastSyncAt)
ON CONFLICT (Id)
DO UPDATE SET LastSyncAt = :LastSyncAt`, params); err != nil {
return "", err
}
}
return attachment.Id, nil
}
// GetAttachment fetches a shared channel file attachment record based on file_id and remoteId.
func (s SqlSharedChannelStore) GetAttachment(fileId string, remoteId string) (*model.SharedChannelAttachment, error) {
var attachment model.SharedChannelAttachment
squery, args, err := s.getQueryBuilder().
Select("*").
From("SharedChannelAttachments").
Where(sq.Eq{"SharedChannelAttachments.FileId": fileId}).
Where(sq.Eq{"SharedChannelAttachments.RemoteId": remoteId}).
ToSql()
if err != nil {
return nil, errors.Wrapf(err, "getsharedchannelattachment_tosql")
}
if err := s.GetReplica().SelectOne(&attachment, squery, args...); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("SharedChannelAttachment", fileId)
}
return nil, errors.Wrapf(err, "failed to find shared channel attachment with FileId=%s, RemoteId=%s", fileId, remoteId)
}
return &attachment, nil
}
// UpdateAttachmentLastSyncAt updates the LastSyncAt timestamp for the specified SharedChannelAttachment.
func (s SqlSharedChannelStore) UpdateAttachmentLastSyncAt(id string, syncTime int64) error {
squery, args, err := s.getQueryBuilder().
Update("SharedChannelAttachments").
Set("LastSyncAt", syncTime).
Where(sq.Eq{"Id": id}).
ToSql()
if err != nil {
return errors.Wrap(err, "update_shared_channel_attachment_last_sync_at_tosql")
}
result, err := s.GetMaster().Exec(squery, args...)
if err != nil {
return errors.Wrap(err, "failed to update LastSycnAt for SharedChannelAttachment")
}
count, err := result.RowsAffected()
if err != nil {
return errors.Wrap(err, "failed to determine rows affected")
}
if count == 0 {
return fmt.Errorf("id not found: %s", id)
}
return nil
}

14
store/sqlstore/shared_channel_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost-server/v5/store/storetest"
)
func TestSharedChannelStore(t *testing.T) {
StoreTestWithSqlStore(t, storetest.TestSharedChannelStore)
}

Просмотреть файл

@@ -101,6 +101,7 @@ type SqlStoreStores struct {
bot store.BotStore
audit store.AuditStore
cluster store.ClusterDiscoveryStore
remoteCluster store.RemoteClusterStore
compliance store.ComplianceStore
session store.SessionStore
oauth store.OAuthStore
@@ -127,6 +128,7 @@ type SqlStoreStores struct {
group store.GroupStore
UserTermsOfService store.UserTermsOfServiceStore
linkMetadata store.LinkMetadataStore
sharedchannel store.SharedChannelStore
}
type SqlStore struct {
@@ -186,6 +188,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
store.stores.bot = newSqlBotStore(store, metrics)
store.stores.audit = newSqlAuditStore(store)
store.stores.cluster = newSqlClusterDiscoveryStore(store)
store.stores.remoteCluster = newSqlRemoteClusterStore(store)
store.stores.compliance = newSqlComplianceStore(store)
store.stores.session = newSqlSessionStore(store)
store.stores.oauth = newSqlOAuthStore(store)
@@ -208,6 +211,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
store.stores.TermsOfService = newSqlTermsOfServiceStore(store, metrics)
store.stores.UserTermsOfService = newSqlUserTermsOfServiceStore(store)
store.stores.linkMetadata = newSqlLinkMetadataStore(store)
store.stores.sharedchannel = newSqlSharedChannelStore(store)
store.stores.reaction = newSqlReactionStore(store)
store.stores.role = newSqlRoleStore(store)
store.stores.scheme = newSqlSchemeStore(store)
@@ -258,8 +262,10 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
store.stores.productNotices.(SqlProductNoticesStore).createIndexesIfNotExists()
store.stores.UserTermsOfService.(SqlUserTermsOfServiceStore).createIndexesIfNotExists()
store.stores.linkMetadata.(*SqlLinkMetadataStore).createIndexesIfNotExists()
store.stores.sharedchannel.(*SqlSharedChannelStore).createIndexesIfNotExists()
store.stores.group.(*SqlGroupStore).createIndexesIfNotExists()
store.stores.scheme.(*SqlSchemeStore).createIndexesIfNotExists()
store.stores.remoteCluster.(*sqlRemoteClusterStore).createIndexesIfNotExists()
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
return store
@@ -1212,6 +1218,10 @@ func (ss *SqlStore) ClusterDiscovery() store.ClusterDiscoveryStore {
return ss.stores.cluster
}
func (ss *SqlStore) RemoteCluster() store.RemoteClusterStore {
return ss.stores.remoteCluster
}
func (ss *SqlStore) Compliance() store.ComplianceStore {
return ss.stores.compliance
}
@@ -1316,6 +1326,10 @@ func (ss *SqlStore) LinkMetadata() store.LinkMetadataStore {
return ss.stores.linkMetadata
}
func (ss *SqlStore) SharedChannel() store.SharedChannelStore {
return ss.stores.sharedchannel
}
func (ss *SqlStore) DropAllTables() {
ss.master.TruncateTables()
}

Просмотреть файл

@@ -961,6 +961,8 @@ func upgradeDatabaseToVersion531(sqlStore *SqlStore) {
}
}
const RemoteClusterSiteURLUniqueIndex = "remote_clusters_site_url_unique"
func hasMissingMigrationsVersion532(sqlStore *SqlStore) bool {
scIdInfo, err := sqlStore.GetColumnInfo("Posts", "FileIds")
if err != nil {
@@ -987,6 +989,7 @@ func upgradeDatabaseToVersion532(sqlStore *SqlStore) {
}
if shouldPerformUpgrade(sqlStore, Version5310, Version5320) {
sqlStore.CreateColumnIfNotExists("ThreadMemberships", "UnreadMentions", "bigint", "bigint", "0")
// Shared channels support
sqlStore.CreateColumnIfNotExistsNoDefault("Channels", "Shared", "tinyint(1)", "boolean")
sqlStore.CreateColumnIfNotExistsNoDefault("Reactions", "UpdateAt", "bigint", "bigint")
sqlStore.CreateColumnIfNotExistsNoDefault("Reactions", "DeleteAt", "bigint", "bigint")
@@ -1011,6 +1014,24 @@ func upgradeDatabaseToVersion535(sqlStore *SqlStore) {
sqlStore.CreateColumnIfNotExists("SidebarCategories", "Collapsed", "tinyint(1)", "boolean", "0")
// Shared channels support
sqlStore.CreateColumnIfNotExistsNoDefault("Reactions", "RemoteId", "VARCHAR(26)", "VARCHAR(26)")
sqlStore.CreateColumnIfNotExistsNoDefault("Users", "RemoteId", "VARCHAR(26)", "VARCHAR(26)")
sqlStore.CreateColumnIfNotExistsNoDefault("Posts", "RemoteId", "VARCHAR(26)", "VARCHAR(26)")
sqlStore.CreateColumnIfNotExistsNoDefault("FileInfo", "RemoteId", "VARCHAR(26)", "VARCHAR(26)")
sqlStore.CreateColumnIfNotExists("UploadSessions", "RemoteId", "VARCHAR(26)", "VARCHAR(26)", "")
sqlStore.CreateColumnIfNotExists("UploadSessions", "ReqFileId", "VARCHAR(26)", "VARCHAR(26)", "")
if _, err := sqlStore.GetMaster().Exec("UPDATE UploadSessions SET RemoteId='', ReqFileId='' WHERE RemoteId IS NULL"); err != nil {
mlog.Error("Error updating RemoteId,ReqFileId in UploadsSession table", mlog.Err(err))
}
uniquenessColumns := []string{"SiteUrl", "RemoteTeamId"}
if sqlStore.DriverName() == model.DATABASE_DRIVER_MYSQL {
uniquenessColumns = []string{"RemoteTeamId", "SiteUrl(168)"}
}
sqlStore.CreateUniqueCompositeIndexIfNotExists(RemoteClusterSiteURLUniqueIndex, "RemoteClusters", uniquenessColumns)
sqlStore.CreateColumnIfNotExistsNoDefault("Channels", "TotalMsgCountRoot", "bigint", "bigint")
// note: setting default 0 on pre-5.0 tables causes test-db-migration script to fail, so this column will be added to ignore list
sqlStore.CreateColumnIfNotExists("ChannelMembers", "MentionCountRoot", "bigint", "bigint", "0")
sqlStore.AlterColumnDefaultIfExists("ChannelMembers", "MentionCountRoot", model.NewString("0"), model.NewString("0"))

Просмотреть файл

@@ -29,6 +29,8 @@ func newSqlUploadSessionStore(sqlStore *SqlStore) store.UploadSessionStore {
table.ColMap("ChannelId").SetMaxSize(26)
table.ColMap("Filename").SetMaxSize(256)
table.ColMap("Path").SetMaxSize(512)
table.ColMap("RemoteId").SetMaxSize(26)
table.ColMap("ReqFileId").SetMaxSize(26)
}
return s
}

Просмотреть файл

@@ -53,7 +53,7 @@ func newSqlUserStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) s
// note: we are providing field names explicitly here to maintain order of columns (needed when using raw queries)
us.usersQuery = us.getQueryBuilder().
Select("u.Id", "u.CreateAt", "u.UpdateAt", "u.DeleteAt", "u.Username", "u.Password", "u.AuthData", "u.AuthService", "u.Email", "u.EmailVerified", "u.Nickname", "u.FirstName", "u.LastName", "u.Position", "u.Roles", "u.AllowMarketing", "u.Props", "u.NotifyProps", "u.LastPasswordUpdate", "u.LastPictureUpdate", "u.FailedAttempts", "u.Locale", "u.Timezone", "u.MfaActive", "u.MfaSecret",
"b.UserId IS NOT NULL AS IsBot", "COALESCE(b.Description, '') AS BotDescription", "COALESCE(b.LastIconUpdate, 0) AS BotLastIconUpdate").
"b.UserId IS NOT NULL AS IsBot", "COALESCE(b.Description, '') AS BotDescription", "COALESCE(b.LastIconUpdate, 0) AS BotLastIconUpdate", "u.RemoteId").
From("Users u").
LeftJoin("Bots b ON ( b.UserId = u.Id )")
@@ -73,6 +73,7 @@ func newSqlUserStore(sqlStore *SqlStore, metrics einterfaces.MetricsInterface) s
table.ColMap("NotifyProps").SetMaxSize(2000)
table.ColMap("Locale").SetMaxSize(5)
table.ColMap("MfaSecret").SetMaxSize(128)
table.ColMap("RemoteId").SetMaxSize(26)
table.ColMap("Position").SetMaxSize(128)
table.ColMap("Timezone").SetMaxSize(256)
}
@@ -101,7 +102,7 @@ func (us SqlUserStore) createIndexesIfNotExists() {
}
func (us SqlUserStore) Save(user *model.User) (*model.User, error) {
if user.Id != "" {
if user.Id != "" && !user.IsRemote() {
return nil, store.NewErrInvalidInput("User", "id", user.Id)
}
@@ -358,7 +359,7 @@ func (us SqlUserStore) Get(ctx context.Context, id string) (*model.User, error)
&user.Nickname, &user.FirstName, &user.LastName, &user.Position, &user.Roles,
&user.AllowMarketing, &props, &notifyProps, &user.LastPasswordUpdate, &user.LastPictureUpdate,
&user.FailedAttempts, &user.Locale, &timezone, &user.MfaActive, &user.MfaSecret,
&user.IsBot, &user.BotDescription, &user.BotLastIconUpdate)
&user.IsBot, &user.BotDescription, &user.BotLastIconUpdate, &user.RemoteId)
if err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("User", id)
@@ -727,7 +728,7 @@ func (us SqlUserStore) GetAllProfilesInChannel(ctx context.Context, channelID st
for rows.Next() {
var user model.User
var props, notifyProps, timezone []byte
if err = rows.Scan(&user.Id, &user.CreateAt, &user.UpdateAt, &user.DeleteAt, &user.Username, &user.Password, &user.AuthData, &user.AuthService, &user.Email, &user.EmailVerified, &user.Nickname, &user.FirstName, &user.LastName, &user.Position, &user.Roles, &user.AllowMarketing, &props, &notifyProps, &user.LastPasswordUpdate, &user.LastPictureUpdate, &user.FailedAttempts, &user.Locale, &timezone, &user.MfaActive, &user.MfaSecret, &user.IsBot, &user.BotDescription, &user.BotLastIconUpdate); err != nil {
if err = rows.Scan(&user.Id, &user.CreateAt, &user.UpdateAt, &user.DeleteAt, &user.Username, &user.Password, &user.AuthData, &user.AuthService, &user.Email, &user.EmailVerified, &user.Nickname, &user.FirstName, &user.LastName, &user.Position, &user.Roles, &user.AllowMarketing, &props, &notifyProps, &user.LastPasswordUpdate, &user.LastPictureUpdate, &user.FailedAttempts, &user.Locale, &timezone, &user.MfaActive, &user.MfaSecret, &user.IsBot, &user.BotDescription, &user.BotLastIconUpdate, &user.RemoteId); err != nil {
return nil, errors.Wrap(err, "failed to scan values from rows into User entity")
}
if err = json.Unmarshal(props, &user.Props); err != nil {