Plugin API hook for Shared Channel file attachment sync (#25874)
* option for auto inviting plugin to all shared channels. * auto-invite remotes to shared channels when flag set * fix unit test * immediately ping new remotes; fix unique siteurl bug * make i18n-extract * fix translations * plugin hooks for file attachments * hook for profile image sync * fix profile image sync * fix unit test --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -66,7 +66,8 @@ func (rcs *Service) pingGenerator(pingChan chan *model.RemoteCluster, done <-cha
|
||||
}
|
||||
|
||||
for _, rc := range remotes {
|
||||
if rc.SiteURL != "" || rc.PluginID != "" { // filter out unconfirmed invites
|
||||
// filter out unconfirmed invites so we don't ping them without permission
|
||||
if rc.IsConfirmed() {
|
||||
pingChan <- rc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ func (scs *Service) sendAttachmentForRemote(fi *model.FileInfo, post *model.Post
|
||||
return fmt.Errorf("cannot update remote cluster for remote id %s; Remote Cluster Service not enabled", rc.RemoteId)
|
||||
}
|
||||
|
||||
if rc.IsPlugin() {
|
||||
return scs.sendAttachmentToPlugin(fi, post, rc)
|
||||
}
|
||||
|
||||
us := &model.UploadSession{
|
||||
Id: model.NewId(),
|
||||
Type: model.UploadTypeAttachment,
|
||||
@@ -120,11 +124,7 @@ func (scs *Service) sendAttachmentForRemote(fi *model.FileInfo, post *model.Post
|
||||
}
|
||||
|
||||
// save file attachment record in SharedChannelAttachments table
|
||||
sca := &model.SharedChannelAttachment{
|
||||
FileId: fi.Id,
|
||||
RemoteId: rc.RemoteId,
|
||||
}
|
||||
if _, err2 := scs.server.GetStore().SharedChannel().UpsertAttachment(sca); err2 != nil {
|
||||
if err2 := scs.saveSharedAttachment(&fi, rc); err2 != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "error saving SharedChannelAttachment",
|
||||
mlog.String("remote", rc.DisplayName),
|
||||
mlog.String("uploadId", usResp.Id),
|
||||
@@ -140,6 +140,24 @@ func (scs *Service) sendAttachmentForRemote(fi *model.FileInfo, post *model.Post
|
||||
})
|
||||
}
|
||||
|
||||
// sendAttachmentToPlugin asynchronously sends a file attachment to a remote cluster.
|
||||
func (scs *Service) sendAttachmentToPlugin(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error {
|
||||
if err := scs.app.OnSharedChannelsAttachmentSyncMsg(fi, post, rc); err != nil {
|
||||
return fmt.Errorf("cannot send attachment to plugin %s: %w", rc.PluginID, err)
|
||||
}
|
||||
return scs.saveSharedAttachment(fi, rc)
|
||||
}
|
||||
|
||||
// saveSharedAttachment saves the attachment in SharedChannelAttachments table.
|
||||
func (scs *Service) saveSharedAttachment(fi *model.FileInfo, rc *model.RemoteCluster) error {
|
||||
sca := &model.SharedChannelAttachment{
|
||||
FileId: fi.Id,
|
||||
RemoteId: rc.RemoteId,
|
||||
}
|
||||
_, err := scs.server.GetStore().SharedChannel().UpsertAttachment(sca)
|
||||
return err
|
||||
}
|
||||
|
||||
// onReceiveUploadCreate is called when a message requesting to create an upload session is received. An upload session is
|
||||
// created and the id returned in the response.
|
||||
func (scs *Service) onReceiveUploadCreate(msg model.RemoteClusterMsg, rc *model.RemoteCluster, response *remotecluster.Response) error {
|
||||
|
||||
@@ -314,6 +314,34 @@ func (_m *MockAppIface) NotifySharedChannelUserUpdate(user *model.User) {
|
||||
_m.Called(user)
|
||||
}
|
||||
|
||||
// OnSharedChannelsAttachmentSyncMsg provides a mock function with given fields: fi, post, rc
|
||||
func (_m *MockAppIface) OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error {
|
||||
ret := _m.Called(fi, post, rc)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.FileInfo, *model.Post, *model.RemoteCluster) error); ok {
|
||||
r0 = rf(fi, post, rc)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// OnSharedChannelsProfileImageSyncMsg provides a mock function with given fields: user, rc
|
||||
func (_m *MockAppIface) OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error {
|
||||
ret := _m.Called(user, rc)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.User, *model.RemoteCluster) error); ok {
|
||||
r0 = rf(user, rc)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// OnSharedChannelsSyncMsg provides a mock function with given fields: msg, rc
|
||||
func (_m *MockAppIface) OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error) {
|
||||
ret := _m.Called(msg, rc)
|
||||
|
||||
@@ -64,6 +64,8 @@ type AppIface interface {
|
||||
InvalidateCacheForUser(userID string)
|
||||
NotifySharedChannelUserUpdate(user *model.User)
|
||||
OnSharedChannelsSyncMsg(msg *model.SyncMsg, rc *model.RemoteCluster) (model.SyncResponse, error)
|
||||
OnSharedChannelsAttachmentSyncMsg(fi *model.FileInfo, post *model.Post, rc *model.RemoteCluster) error
|
||||
OnSharedChannelsProfileImageSyncMsg(user *model.User, rc *model.RemoteCluster) error
|
||||
}
|
||||
|
||||
// errNotFound allows checking against Store.ErrNotFound errors without making Store a dependency.
|
||||
|
||||
@@ -402,11 +402,17 @@ func (scs *Service) shouldUserSync(user *model.User, channelID string, rc *model
|
||||
}
|
||||
if _, err = scs.server.GetStore().SharedChannel().SaveUser(scu); err != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error adding user to shared channel users",
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
mlog.String("user_id", user.Id),
|
||||
mlog.String("channel_id", user.Id),
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
mlog.Err(err),
|
||||
)
|
||||
} else {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Added user to shared channel users",
|
||||
mlog.String("user_id", user.Id),
|
||||
mlog.String("channel_id", user.Id),
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
)
|
||||
}
|
||||
return true, true, nil
|
||||
}
|
||||
@@ -420,30 +426,55 @@ func (scs *Service) syncProfileImage(user *model.User, channelID string, rc *mod
|
||||
return
|
||||
}
|
||||
|
||||
if rc.IsPlugin() {
|
||||
scs.sendProfileImageToPlugin(user, channelID, rc)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ProfileImageSyncTimeout)
|
||||
defer cancel()
|
||||
|
||||
rcs.SendProfileImage(ctx, user.Id, rc, scs.app, func(userId string, rc *model.RemoteCluster, resp *remotecluster.Response, err error) {
|
||||
if resp.IsSuccess() {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Users profile image synchronized",
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
mlog.String("user_id", user.Id),
|
||||
)
|
||||
|
||||
if err2 := scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(user.Id, channelID, rc.RemoteId); err2 != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error updating users LastSyncTime after profile image update",
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
mlog.String("user_id", user.Id),
|
||||
mlog.Err(err2),
|
||||
)
|
||||
}
|
||||
scs.recordProfileImageSuccess(user.Id, channelID, rc.RemoteId)
|
||||
return
|
||||
}
|
||||
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error synchronizing users profile image",
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
mlog.String("user_id", user.Id),
|
||||
mlog.String("channel_id", channelID),
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
mlog.Err(err),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (scs *Service) sendProfileImageToPlugin(user *model.User, channelID string, rc *model.RemoteCluster) {
|
||||
if err := scs.app.OnSharedChannelsProfileImageSyncMsg(user, rc); err != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error synchronizing users profile image for plugin",
|
||||
mlog.String("user_id", user.Id),
|
||||
mlog.String("channel_id", channelID),
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
scs.recordProfileImageSuccess(user.Id, channelID, rc.RemoteId)
|
||||
}
|
||||
|
||||
func (scs *Service) recordProfileImageSuccess(userID, channelID, remoteID string) {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceDebug, "Users profile image synchronized",
|
||||
mlog.String("user_id", userID),
|
||||
mlog.String("channel_id", channelID),
|
||||
mlog.String("remote_id", remoteID),
|
||||
)
|
||||
|
||||
// update LastSyncAt for user in SharedChannelUsers table
|
||||
if err := scs.server.GetStore().SharedChannel().UpdateUserLastSyncAt(userID, channelID, remoteID); err != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Error updating users LastSyncTime after profile image update",
|
||||
mlog.String("user_id", userID),
|
||||
mlog.String("channel_id", channelID),
|
||||
mlog.String("remote_id", remoteID),
|
||||
mlog.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,13 +235,17 @@ func (scs *Service) fetchPostsForSync(sd *syncData) error {
|
||||
count := len(posts)
|
||||
sd.posts = appendPosts(sd.posts, posts, scs.server.GetStore().Post(), cursor.LastPostCreateAt)
|
||||
|
||||
cache := postsSliceToMap(posts)
|
||||
|
||||
// Fill remaining batch capacity with updated posts.
|
||||
if len(posts) < MaxPostsPerSync {
|
||||
options.SinceCreateAt = false
|
||||
// use 'nextcursor' as it has the correct xxxUpdateAt values, and the updsted xxxCreateAt values.
|
||||
posts, nextCursor, err = scs.server.GetStore().Post().GetPostsSinceForSync(options, nextCursor, MaxPostsPerSync-len(posts))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch modified posts for sync: %w", err)
|
||||
}
|
||||
posts = reducePostsSliceInCache(posts, cache)
|
||||
count += len(posts)
|
||||
sd.posts = appendPosts(sd.posts, posts, scs.server.GetStore().Post(), cursor.LastPostUpdateAt)
|
||||
}
|
||||
|
||||
@@ -130,3 +130,21 @@ func isNotFoundError(err error) bool {
|
||||
var errNotFound *store.ErrNotFound
|
||||
return errors.As(err, &errNotFound)
|
||||
}
|
||||
|
||||
func postsSliceToMap(posts []*model.Post) map[string]*model.Post {
|
||||
m := make(map[string]*model.Post, len(posts))
|
||||
for _, p := range posts {
|
||||
m[p.Id] = p
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func reducePostsSliceInCache(posts []*model.Post, cache map[string]*model.Post) []*model.Post {
|
||||
reduced := make([]*model.Post, 0, len(posts))
|
||||
for _, p := range posts {
|
||||
if _, ok := cache[p.Id]; !ok {
|
||||
reduced = append(reduced, p)
|
||||
}
|
||||
}
|
||||
return reduced
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user