[MM-59519] preserve DM/GM unread/read state over export and import (#27707)

Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2024-08-26 17:04:48 +02:00
коммит произвёл GitHub
родитель c2c37cea20
Коммит 4b3de1861f
14 изменённых файлов: 1138 добавлений и 556 удалений

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

@@ -815,7 +815,7 @@ func (a *App) exportAllDirectChannels(ctx request.CTX, job *model.Job, writer io
afterId = channel.Id afterId = channel.Id
// Skip if there are no active members in the channel // Skip if there are no active members in the channel
if len(*channel.Members) == 0 { if len(channel.Members) == 0 {
continue continue
} }
@@ -829,7 +829,12 @@ func (a *App) exportAllDirectChannels(ctx request.CTX, job *model.Job, writer io
return err return err
} }
channelLine := ImportLineFromDirectChannel(channel, favoritedBy) shownBy, err := a.buildShownByList(channel)
if err != nil {
return err
}
channelLine := ImportLineFromDirectChannel(channel, favoritedBy, shownBy)
if err := a.exportWriteLine(writer, channelLine); err != nil { if err := a.exportWriteLine(writer, channelLine); err != nil {
return err return err
} }
@@ -862,6 +867,55 @@ func (a *App) buildFavoritedByList(channelID string) ([]string, *model.AppError)
return userIDs, nil return userIDs, nil
} }
func (a *App) buildShownByList(channel *model.DirectChannelForExport) ([]string, *model.AppError) {
shownBy := make([]string, 0)
switch channel.Type {
case model.ChannelTypeGroup:
for _, member := range channel.Members {
prefs, err := a.Srv().Store().Preference().GetCategory(member.UserId, model.PreferenceCategoryGroupChannelShow)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.preference.get_category.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
for i := range prefs {
if prefs[i].Name == channel.Id && prefs[i].Value == "true" {
user, err := a.Srv().Store().User().Get(context.Background(), member.UserId)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
shownBy = append(shownBy, user.Username)
}
}
}
case model.ChannelTypeDirect:
for i, member := range channel.Members {
otherMember := member // in case it's a channel with self
if len(channel.Members) == 2 {
// since the are only two members, the other member is should be the remainder of i+1/2
otherMember = channel.Members[(i+1)%2]
}
prefs, err := a.Srv().Store().Preference().GetCategoryAndName(model.PreferenceCategoryDirectChannelShow, otherMember.UserId)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.preference.get_category.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
for _, pref := range prefs {
if pref.Value == "true" && pref.UserId == member.UserId {
user, err := a.Srv().Store().User().Get(context.Background(), member.UserId)
if err != nil {
return nil, model.NewAppError("buildShownByList", "app.user.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
shownBy = append(shownBy, user.Username)
}
}
}
}
return shownBy, nil
}
func (a *App) exportAllDirectPosts(ctx request.CTX, job *model.Job, writer io.Writer, withAttachments, includeArchivedChannels bool) ([]imports.AttachmentImportData, *model.AppError) { func (a *App) exportAllDirectPosts(ctx request.CTX, job *model.Job, writer io.Writer, withAttachments, includeArchivedChannels bool) ([]imports.AttachmentImportData, *model.AppError) {
var attachments []imports.AttachmentImportData var attachments []imports.AttachmentImportData
afterId := strings.Repeat("0", 26) afterId := strings.Repeat("0", 26)

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

@@ -40,17 +40,17 @@ func ImportLineFromChannel(channel *model.ChannelForExport) *imports.LineImportD
} }
} }
func ImportLineFromDirectChannel(channel *model.DirectChannelForExport, favoritedBy []string) *imports.LineImportData { func ImportLineFromDirectChannel(channel *model.DirectChannelForExport, favoritedBy, shownBy []string) *imports.LineImportData {
channelMembers := *channel.Members channelMembers := channel.Members
if len(channelMembers) == 1 { if len(channelMembers) == 1 {
channelMembers = []string{channelMembers[0], channelMembers[0]} channelMembers = []*model.ChannelMemberForExport{channelMembers[0], channelMembers[0]}
} }
line := &imports.LineImportData{ line := &imports.LineImportData{
Type: "direct_channel", Type: "direct_channel",
DirectChannel: &imports.DirectChannelImportData{ DirectChannel: &imports.DirectChannelImportData{
Header: &channel.Header, Header: &channel.Header,
Members: &channelMembers, Participants: importDirectChannelMembersFromChannelMembers(channelMembers),
}, },
} }
@@ -58,9 +58,82 @@ func ImportLineFromDirectChannel(channel *model.DirectChannelForExport, favorite
line.DirectChannel.FavoritedBy = &favoritedBy line.DirectChannel.FavoritedBy = &favoritedBy
} }
if len(shownBy) != 0 {
line.DirectChannel.ShownBy = &shownBy
}
return line return line
} }
func importDirectChannelMembersFromChannelMembers(members []*model.ChannelMemberForExport) []*imports.DirectChannelMemberImportData {
importedMembers := make([]*imports.DirectChannelMemberImportData, len(members))
for i, member := range members {
props := member.NotifyProps
notifyProps := imports.UserChannelNotifyPropsImportData{}
desktop, exist := props[model.DesktopNotifyProp]
if exist {
notifyProps.Desktop = &desktop
}
mobile, exist := props[model.PushNotifyProp]
if exist {
notifyProps.Mobile = &mobile
}
email, exist := props[model.EmailNotifyProp]
if exist {
notifyProps.Email = &email
}
ignoreMentions, exist := props[model.IgnoreChannelMentionsNotifyProp]
if exist {
notifyProps.IgnoreChannelMentions = &ignoreMentions
}
channelAutoFallow, exist := props[model.ChannelAutoFollowThreads]
if exist {
notifyProps.ChannelAutoFollowThreads = &channelAutoFallow
}
markUnread, exist := props[model.MarkUnreadNotifyProp]
if exist {
notifyProps.MarkUnread = &markUnread
}
dcm := &imports.DirectChannelMemberImportData{
Username: &member.Username,
NotifyProps: &notifyProps,
}
if member.SchemeUser {
dcm.SchemeUser = &member.SchemeUser
}
if member.SchemeAdmin {
dcm.SchemeAdmin = &member.SchemeAdmin
}
if member.SchemeGuest {
dcm.SchemeGuest = &member.SchemeGuest
}
if member.LastViewedAt != 0 {
dcm.LastViewedAt = &member.LastViewedAt
}
if member.MentionCount != 0 {
dcm.MentionCount = &member.MentionCount
}
if member.MentionCountRoot != 0 {
dcm.MentionCountRoot = &member.MentionCountRoot
}
if member.MsgCount != 0 {
dcm.MsgCount = &member.MsgCount
}
if member.MsgCountRoot != 0 {
dcm.MsgCountRoot = &member.MsgCountRoot
}
if member.UrgentMentionCount != 0 {
dcm.UrgentMentionCount = &member.UrgentMentionCount
}
importedMembers[i] = dcm
}
return importedMembers
}
func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *imports.LineImportData { func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *imports.LineImportData {
// Bulk Importer doesn't accept "empty string" for AuthService. // Bulk Importer doesn't accept "empty string" for AuthService.
var authService *string var authService *string

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

@@ -269,8 +269,9 @@ func TestExportDMChannel(t *testing.T) {
// Ensure the Members of the imported DM channel is the same was from the exported // Ensure the Members of the imported DM channel is the same was from the exported
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false) channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
require.NoError(t, nErr) require.NoError(t, nErr)
require.Equal(t, 1, len(channels)) require.Len(t, channels, 1)
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, *channels[0].Members) require.Len(t, channels[0].Members, 2)
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, []string{channels[0].Members[0].Username, channels[0].Members[1].Username})
// Ensure the favorited channel was retained // Ensure the favorited channel was retained
fav, nErr := th2.App.Srv().Store().Preference().Get(th2.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channels[0].Id) fav, nErr := th2.App.Srv().Store().Preference().Get(th2.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channels[0].Id)
@@ -340,8 +341,8 @@ func TestExportDMChannelToSelf(t *testing.T) {
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false) channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000", false)
require.NoError(t, nErr) require.NoError(t, nErr)
assert.Equal(t, 1, len(channels)) assert.Equal(t, 1, len(channels))
assert.Equal(t, 1, len((*channels[0].Members))) assert.Equal(t, 1, len((channels[0].Members)))
assert.Equal(t, th1.BasicUser.Username, (*channels[0].Members)[0]) assert.Equal(t, th1.BasicUser.Username, channels[0].Members[0].Username)
} }
func TestExportGMChannel(t *testing.T) { func TestExportGMChannel(t *testing.T) {
@@ -416,8 +417,8 @@ func TestExportGMandDMChannels(t *testing.T) {
// Adding some determinism so its possible to assert on slice index // Adding some determinism so its possible to assert on slice index
sort.Slice(channels, func(i, j int) bool { return channels[i].Type > channels[j].Type }) sort.Slice(channels, func(i, j int) bool { return channels[i].Type > channels[j].Type })
assert.Equal(t, 2, len(channels)) assert.Equal(t, 2, len(channels))
assert.ElementsMatch(t, []string{th1.BasicUser.Username, user1.Username, user2.Username}, *channels[0].Members) assert.ElementsMatch(t, []string{th1.BasicUser.Username, user1.Username, user2.Username}, []string{channels[0].Members[0].Username, channels[0].Members[1].Username, channels[0].Members[2].Username})
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, *channels[1].Members) assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, []string{channels[1].Members[0].Username, channels[1].Members[1].Username})
} }
func TestExportDMandGMPost(t *testing.T) { func TestExportDMandGMPost(t *testing.T) {

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

@@ -1758,40 +1758,187 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
return nil return nil
} }
var members []string
if data.Participants != nil {
members = make([]string, len(data.Participants))
for i, member := range data.Participants {
members[i] = *member.Username
}
} else if data.Members != nil {
members = make([]string, len(*data.Members))
copy(members, *data.Members)
} else {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.no_members.error", nil, "", http.StatusBadRequest)
}
var userIDs []string var userIDs []string
userMap, err := a.getUsersByUsernames(*data.Members) userMap, err := a.getUsersByUsernames(members)
if err != nil { if err != nil {
return err return err
} }
for _, user := range *data.Members { for _, user := range members {
userIDs = append(userIDs, userMap[strings.ToLower(user)].Id) userIDs = append(userIDs, userMap[strings.ToLower(user)].Id)
} }
var channel *model.Channel var channel *model.Channel
if len(userIDs) == 2 { if len(userIDs) == 2 {
ch, err := a.createDirectChannel(rctx, userIDs[0], userIDs[1]) ch, err2 := a.createDirectChannel(rctx, userIDs[0], userIDs[1])
if err != nil && err.Id != store.ChannelExistsError { if err2 != nil && err2.Id != store.ChannelExistsError {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, "", http.StatusBadRequest).Wrap(err) return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, "", http.StatusBadRequest).Wrap(err2)
} }
channel = ch channel = ch
} else { } else {
ch, err := a.createGroupChannel(rctx, userIDs) ch, err2 := a.createGroupChannel(rctx, userIDs)
if err != nil && err.Id != store.ChannelExistsError { if err2 != nil && err2.Id != store.ChannelExistsError {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err) return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(err2)
} }
channel = ch channel = ch
} }
totalMembers, err := a.GetChannelMemberCount(rctx, channel.Id)
if err != nil {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.get_channel_members.error", nil, "", http.StatusBadRequest).Wrap(err)
}
var ems = make([]model.ChannelMember, 0, totalMembers)
var page int
for int64(len(ems)) < totalMembers {
res, err := a.GetChannelMembersPage(rctx, channel.Id, page, 100)
if err != nil {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.get_channel_members.error", nil, "", http.StatusBadRequest).Wrap(err)
}
ems = append(ems, res...)
page++
}
existingMembers := make(map[string]model.ChannelMember)
for _, member := range ems {
existingMembers[member.UserId] = member
}
newChannelMembers := make([]*model.ChannelMember, 0)
for _, member := range data.Participants {
m := &model.ChannelMember{
NotifyProps: model.GetDefaultChannelNotifyProps(),
}
if member.LastViewedAt != nil {
m.LastViewedAt = *member.LastViewedAt
}
if member.MsgCount != nil {
m.MsgCount = *member.MsgCount
}
if member.MentionCount != nil {
m.MentionCount = *member.MentionCount
}
if member.MentionCountRoot != nil {
m.MentionCountRoot = *member.MentionCountRoot
}
if member.UrgentMentionCount != nil {
m.UrgentMentionCount = *member.UrgentMentionCount
}
if member.MsgCountRoot != nil {
m.MsgCountRoot = *member.MsgCountRoot
}
if member.SchemeUser != nil {
m.SchemeUser = *member.SchemeUser
}
if member.SchemeAdmin != nil {
m.SchemeAdmin = *member.SchemeAdmin
}
if member.SchemeGuest != nil {
m.SchemeGuest = *member.SchemeGuest
}
if member.NotifyProps != nil {
if member.NotifyProps.Desktop != nil {
if value, ok := m.NotifyProps[model.DesktopNotifyProp]; !ok || value != *member.NotifyProps.Desktop {
m.NotifyProps[model.DesktopNotifyProp] = *member.NotifyProps.Desktop
}
}
if member.NotifyProps.MarkUnread != nil {
if value, ok := m.NotifyProps[model.DesktopSoundNotifyProp]; !ok || value != *member.NotifyProps.MarkUnread {
m.NotifyProps[model.MarkUnreadNotifyProp] = *member.NotifyProps.MarkUnread
}
}
if member.NotifyProps.Mobile != nil {
if value, ok := m.NotifyProps[model.PushNotifyProp]; !ok || value != *member.NotifyProps.Mobile {
m.NotifyProps[model.PushNotifyProp] = *member.NotifyProps.Mobile
}
}
if member.NotifyProps.Email != nil {
if value, ok := m.NotifyProps[model.EmailNotifyProp]; !ok || value != *member.NotifyProps.Email {
m.NotifyProps[model.EmailNotifyProp] = *member.NotifyProps.Email
}
}
if member.NotifyProps.IgnoreChannelMentions != nil {
if value, ok := m.NotifyProps[model.IgnoreChannelMentionsNotifyProp]; !ok || value != *member.NotifyProps.IgnoreChannelMentions {
m.NotifyProps[model.IgnoreChannelMentionsNotifyProp] = *member.NotifyProps.IgnoreChannelMentions
}
}
if member.NotifyProps.ChannelAutoFollowThreads != nil {
if value, ok := m.NotifyProps[model.ChannelAutoFollowThreads]; !ok || value != *member.NotifyProps.ChannelAutoFollowThreads {
m.NotifyProps[model.ChannelAutoFollowThreads] = *member.NotifyProps.ChannelAutoFollowThreads
}
}
}
u := userMap[strings.ToLower(*member.Username)]
if existing, ok := existingMembers[u.Id]; ok {
// Decide which membership is newer. We have LastViewedAt in the import data, which should
// give us a good idea of which membership is newer.
if existing.LastViewedAt > m.LastViewedAt {
continue
}
}
m.UserId = u.Id
m.ChannelId = channel.Id
newChannelMembers = append(newChannelMembers, m)
}
// the channel memberships are already created in the channel creation
// we always going to update the channel memberships
if len(newChannelMembers) > 0 {
_, nErr := a.Srv().Store().Channel().UpdateMultipleMembers(newChannelMembers)
if nErr != nil {
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, "", http.StatusBadRequest).Wrap(nErr)
}
}
var preferences model.Preferences var preferences model.Preferences
for _, userID := range userIDs { if data.ShownBy != nil {
preferences = append(preferences, model.Preference{ for _, username := range *data.ShownBy {
UserId: userID, switch channel.Type {
Category: model.PreferenceCategoryDirectChannelShow, case model.ChannelTypeDirect:
Name: channel.Id, otherUserId := userMap[strings.ToLower(username)].Id
Value: "true", for uname, user := range userMap {
}) if uname != username {
otherUserId = user.Id
break
}
}
preferences = append(preferences, model.Preference{
UserId: userMap[strings.ToLower(username)].Id,
Category: model.PreferenceCategoryDirectChannelShow,
Name: otherUserId,
Value: "true",
})
case model.ChannelTypeGroup:
preferences = append(preferences, model.Preference{
UserId: userMap[strings.ToLower(username)].Id,
Category: model.PreferenceCategoryGroupChannelShow,
Name: channel.Id,
Value: "true",
})
}
}
} }
if data.FavoritedBy != nil { if data.FavoritedBy != nil {
@@ -1805,14 +1952,16 @@ func (a *App) importDirectChannel(rctx request.CTX, data *imports.DirectChannelI
} }
} }
if err := a.Srv().Store().Preference().Save(preferences); err != nil { if len(preferences) > 0 {
var appErr *model.AppError if err := a.Srv().Store().Preference().Save(preferences); err != nil {
switch { var appErr *model.AppError
case errors.As(err, &appErr): switch {
appErr.StatusCode = http.StatusBadRequest case errors.As(err, &appErr):
return appErr appErr.StatusCode = http.StatusBadRequest
default: return appErr
return model.NewAppError("importDirectChannel", "app.preference.save.updating.app_error", nil, "", http.StatusBadRequest).Wrap(err) default:
return model.NewAppError("importDirectChannel", "app.preference.save.updating.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
} }
} }

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

@@ -3161,6 +3161,7 @@ func TestImportImportPost(t *testing.T) {
func TestImportImportDirectChannel(t *testing.T) { func TestImportImportDirectChannel(t *testing.T) {
th := Setup(t).InitBasic() th := Setup(t).InitBasic()
defer th.TearDown() defer th.TearDown()
user3 := th.CreateUser()
// Check how many channels are in the database. // Check how many channels are in the database.
directChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeDirect) directChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeDirect)
@@ -3169,161 +3170,363 @@ func TestImportImportDirectChannel(t *testing.T) {
groupChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeGroup) groupChannelCount, err := th.App.Srv().Store().Channel().AnalyticsTypeCount("", model.ChannelTypeGroup)
require.NoError(t, err, "Failed to get group channel count.") require.NoError(t, err, "Failed to get group channel count.")
// Do an invalid channel in dry-run mode. // We need to generate the dataset twice to test the same data with different formats.
data := imports.DirectChannelImportData{ generateDataset := func(data imports.DirectChannelImportData) map[string]imports.DirectChannelImportData {
Members: &[]string{ members := make([]string, len(data.Participants))
model.NewId(), for i, member := range data.Participants {
}, members[i] = *member.Username
Header: ptrStr("Channel Header"), }
return map[string]imports.DirectChannelImportData{
"Participants": data,
"Members": {
Members: &members,
},
}
} }
err = th.App.importDirectChannel(th.Context, &data, true)
require.Error(t, err)
// Check that no more channels are in the DB. t.Run("Invalid channel in dry-run mode", func(t *testing.T) {
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount) dataset := generateDataset(imports.DirectChannelImportData{
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
},
Header: ptrStr("Channel Header"),
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
err = th.App.importDirectChannel(th.Context, &data, true)
require.Error(t, err)
// Do a valid DIRECT channel with a nonexistent member in dry-run mode. // Check that no more channels are in the DB.
data.Members = &[]string{ AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
model.NewId(), AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
model.NewId(), })
} }
appErr := th.App.importDirectChannel(th.Context, &data, true) })
require.Nil(t, appErr)
// Check that no more channels are in the DB. t.Run("Valid DIRECT channel with a nonexistent member in dry-run mode", func(t *testing.T) {
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount) dataset := generateDataset(imports.DirectChannelImportData{
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Do a valid GROUP channel with a nonexistent member in dry-run mode. // Check that no more channels are in the DB.
data.Members = &[]string{ AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
model.NewId(), AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
model.NewId(), })
model.NewId(), }
} })
appErr = th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Check that no more channels are in the DB. t.Run("Valid GROUP channel with a nonexistent member in dry-run mode", func(t *testing.T) {
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount) dataset := generateDataset(imports.DirectChannelImportData{
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
{
Username: model.NewString(model.NewId()),
},
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, true)
require.Nil(t, appErr)
// Do an invalid channel in apply mode. // Check that no more channels are in the DB.
data.Members = &[]string{ AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
model.NewId(), AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
} })
err = th.App.importDirectChannel(th.Context, &data, false) }
require.Error(t, err) })
// Check that no more channels are in the DB. t.Run("Invalid channel in apply mode", func(t *testing.T) {
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount) dataset := generateDataset(imports.DirectChannelImportData{
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
err = th.App.importDirectChannel(th.Context, &data, false)
require.Error(t, err)
// Do a valid DIRECT channel. // Check that no more channels are in the DB.
data.Members = &[]string{ AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount)
th.BasicUser.Username, AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
th.BasicUser2.Username, })
} }
appErr = th.App.importDirectChannel(th.Context, &data, false) })
require.Nil(t, appErr)
// Check that one more DIRECT channel is in the DB. t.Run("Valid DIRECT channel ", func(t *testing.T) {
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1) dataset := generateDataset(imports.DirectChannelImportData{
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Do the same DIRECT channel again. // Check that one more DIRECT channel is in the DB.
appErr = th.App.importDirectChannel(th.Context, &data, false) AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
require.Nil(t, appErr) AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
// Check that no more channels are in the DB. // Do the same DIRECT channel again.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1) appErr = th.App.importDirectChannel(th.Context, &data, false)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) require.Nil(t, appErr)
// Update the channel's HEADER // Check that no more channels are in the DB.
data.Header = ptrStr("New Channel Header 2") AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
appErr = th.App.importDirectChannel(th.Context, &data, false) AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
require.Nil(t, appErr)
// Check that no more channels are in the DB. // Update the channel's HEADER
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1) data.Header = ptrStr("New Channel Header 2")
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Get the channel to check that the header was updated. // Check that no more channels are in the DB.
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id) AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
require.Nil(t, appErr) AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
require.Equal(t, channel.Header, *data.Header)
// Do a GROUP channel with an extra invalid member. // Get the channel to check that the header was updated.
user3 := th.CreateUser() channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
data.Members = &[]string{ require.Nil(t, appErr)
th.BasicUser.Username, require.Equal(t, channel.Header, *data.Header)
th.BasicUser2.Username, })
user3.Username, }
model.NewId(), })
}
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.NotNil(t, appErr)
// Check that no more channels are in the DB. t.Run("GROUP channel with an extra invalid member", func(t *testing.T) {
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1) dataset := generateDataset(imports.DirectChannelImportData{
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount) Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
{
Username: model.NewString(model.NewId()),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.NotNil(t, appErr)
// Do a valid GROUP channel. // Check that no more channels are in the DB.
data.Members = &[]string{ AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
th.BasicUser.Username, AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount)
th.BasicUser2.Username, })
user3.Username, }
} })
appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Check that one more GROUP channel is in the DB. t.Run("Valid GROUP channel", func(t *testing.T) {
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1) dataset := generateDataset(imports.DirectChannelImportData{
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1) Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Do the same DIRECT channel again. // Check that one more GROUP channel is in the DB.
appErr = th.App.importDirectChannel(th.Context, &data, false) AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
require.Nil(t, appErr) AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
// Check that no more channels are in the DB. // Do the same DIRECT channel again.
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1) appErr = th.App.importDirectChannel(th.Context, &data, false)
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1) require.Nil(t, appErr)
// Update the channel's HEADER // Check that no more channels are in the DB.
data.Header = ptrStr("New Channel Header 3") AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
appErr = th.App.importDirectChannel(th.Context, &data, false) AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
require.Nil(t, appErr)
// Check that no more channels are in the DB. // Update the channel's HEADER
AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1) data.Header = ptrStr("New Channel Header 3")
AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1) appErr = th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
// Get the channel to check that the header was updated. // Check that no more channels are in the DB.
userIDs := []string{ AssertChannelCount(t, th.App, model.ChannelTypeDirect, directChannelCount+1)
th.BasicUser.Id, AssertChannelCount(t, th.App, model.ChannelTypeGroup, groupChannelCount+1)
th.BasicUser2.Id,
user3.Id,
}
channel, appErr = th.App.createGroupChannel(th.Context, userIDs)
require.Equal(t, appErr.Id, store.ChannelExistsError)
require.Equal(t, channel.Header, *data.Header)
// Import a channel with some favorites. // Get the channel to check that the header was updated.
data.Members = &[]string{ userIDs := []string{
th.BasicUser.Username, th.BasicUser.Id,
th.BasicUser2.Username, th.BasicUser2.Id,
} user3.Id,
data.FavoritedBy = &[]string{ }
th.BasicUser.Username, channel, appErr := th.App.createGroupChannel(th.Context, userIDs)
th.BasicUser2.Username, require.Equal(t, appErr.Id, store.ChannelExistsError)
} require.Equal(t, channel.Header, *data.Header)
appErr = th.App.importDirectChannel(th.Context, &data, false) })
require.Nil(t, appErr) }
})
channel, appErr = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id) t.Run("Import a channel with some favorites", func(t *testing.T) {
require.Nil(t, appErr) dataset := generateDataset(imports.DirectChannelImportData{
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true") Participants: []*imports.DirectChannelMemberImportData{
checkPreference(t, th.App, th.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true") {
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
})
for name, data := range dataset {
t.Run(name, func(t *testing.T) {
data.FavoritedBy = &[]string{
th.BasicUser.Username,
th.BasicUser2.Username,
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
checkPreference(t, th.App, th.BasicUser2.Id, model.PreferenceCategoryFavoriteChannel, channel.Id, "true")
})
}
})
t.Run("Import a DM channel and user last view should be imported", func(t *testing.T) {
lastView := model.GetMillis()
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
LastViewedAt: ptrInt64(lastView),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 2)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
require.Equal(t, member.LastViewedAt, lastView)
}
}
})
t.Run("Import a DM channel and preserve if the channel was shown to users", func(t *testing.T) {
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
},
ShownBy: &[]string{
th.BasicUser.Username,
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 2)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryDirectChannelShow, th.BasicUser2.Id, "true")
}
}
})
t.Run("Import a GM channel and preserve if the channel was shown to users", func(t *testing.T) {
data := imports.DirectChannelImportData{
Participants: []*imports.DirectChannelMemberImportData{
{
Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
},
ShownBy: &[]string{
th.BasicUser.Username,
},
}
appErr := th.App.importDirectChannel(th.Context, &data, false)
require.Nil(t, appErr)
channel, appErr := th.App.GetGroupChannel(th.Context, []string{th.BasicUser.Id, th.BasicUser2.Id, user3.Id})
require.Nil(t, appErr)
members, appErr := th.App.GetChannelMembersPage(th.Context, channel.Id, 0, 100)
require.Nil(t, appErr)
require.Len(t, members, 3)
for _, member := range members {
if member.UserId == th.BasicUser.Id {
checkPreference(t, th.App, th.BasicUser.Id, model.PreferenceCategoryGroupChannelShow, channel.Id, "true")
}
}
})
} }
func TestImportImportDirectPost(t *testing.T) { func TestImportImportDirectPost(t *testing.T) {
@@ -3332,9 +3535,13 @@ func TestImportImportDirectPost(t *testing.T) {
// Create the DIRECT channel. // Create the DIRECT channel.
channelData := imports.DirectChannelImportData{ channelData := imports.DirectChannelImportData{
Members: &[]string{ Participants: []*imports.DirectChannelMemberImportData{
th.BasicUser.Username, {
th.BasicUser2.Username, Username: model.NewString(th.BasicUser.Username),
},
{
Username: model.NewString(th.BasicUser2.Username),
},
}, },
} }
appErr := th.App.importDirectChannel(th.Context, &channelData, false) appErr := th.App.importDirectChannel(th.Context, &channelData, false)
@@ -3688,10 +3895,16 @@ func TestImportImportDirectPost(t *testing.T) {
// Create the GROUP channel. // Create the GROUP channel.
user3 := th.CreateUser() user3 := th.CreateUser()
channelData = imports.DirectChannelImportData{ channelData = imports.DirectChannelImportData{
Members: &[]string{ Participants: []*imports.DirectChannelMemberImportData{
th.BasicUser.Username, {
th.BasicUser2.Username, Username: model.NewString(th.BasicUser.Username),
user3.Username, },
{
Username: model.NewString(th.BasicUser2.Username),
},
{
Username: model.NewString(user3.Username),
},
}, },
} }
appErr = th.App.importDirectChannel(th.Context, &channelData, false) appErr = th.App.importDirectChannel(th.Context, &channelData, false)

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

@@ -123,10 +123,27 @@ type UserChannelImportData struct {
LastViewedAt *int64 `json:"last_viewed_at,omitempty"` LastViewedAt *int64 `json:"last_viewed_at,omitempty"`
} }
type DirectChannelMemberImportData struct {
Username *string `json:"username"`
NotifyProps *UserChannelNotifyPropsImportData `json:"notify_props,omitempty"`
SchemeUser *bool `json:"scheme_user,omitempty"`
SchemeAdmin *bool `json:"scheme_admin,omitempty"`
SchemeGuest *bool `json:"scheme_guest,omitempty"`
MentionCount *int64 `json:"mention_count,omitempty"`
MentionCountRoot *int64 `json:"mention_count_root,omitempty"`
UrgentMentionCount *int64 `json:"urgend_mention_count,omitempty"`
MsgCount *int64 `json:"msg_count,omitempty"`
MsgCountRoot *int64 `json:"msg_count_root,omitempty"`
LastViewedAt *int64 `json:"last_viewed_at,omitempty"`
}
type UserChannelNotifyPropsImportData struct { type UserChannelNotifyPropsImportData struct {
Desktop *string `json:"desktop"` Desktop *string `json:"desktop"`
Mobile *string `json:"mobile"` Mobile *string `json:"mobile"`
MarkUnread *string `json:"mark_unread"` MarkUnread *string `json:"mark_unread"`
Email *string `json:"email,omitempty"`
IgnoreChannelMentions *string `json:"ignore_channel_mentions,omitempty"`
ChannelAutoFollowThreads *string `json:"channel_auto_follow_threads,omitempty"`
} }
type EmojiImportData struct { type EmojiImportData struct {
@@ -173,8 +190,10 @@ type PostImportData struct {
} }
type DirectChannelImportData struct { type DirectChannelImportData struct {
Members *[]string `json:"members"` Members *[]string `json:"members,omitempty"`
FavoritedBy *[]string `json:"favorited_by"` Participants []*DirectChannelMemberImportData `json:"participants,omitempty"`
FavoritedBy *[]string `json:"favorited_by,omitempty"`
ShownBy *[]string `json:"shown_by,omitempty"`
Header *string `json:"header"` Header *string `json:"header"`
} }

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

@@ -489,11 +489,19 @@ func ValidatePostImportData(data *PostImportData, maxPostSize int) *model.AppErr
} }
func ValidateDirectChannelImportData(data *DirectChannelImportData) *model.AppError { func ValidateDirectChannelImportData(data *DirectChannelImportData) *model.AppError {
if data.Members == nil { if data.Participants == nil && data.Members == nil {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_required.error", nil, "", http.StatusBadRequest) return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_required.error", nil, "", http.StatusBadRequest)
} }
if len(*data.Members) != 2 { if data.Participants != nil && len(data.Participants) != 2 {
if len(data.Participants) < model.ChannelGroupMinUsers {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_few.error", nil, "", http.StatusBadRequest)
} else if len(data.Participants) > model.ChannelGroupMaxUsers {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_many.error", nil, "", http.StatusBadRequest)
}
}
if data.Members != nil && len(*data.Members) != 2 {
if len(*data.Members) < model.ChannelGroupMinUsers { if len(*data.Members) < model.ChannelGroupMinUsers {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_few.error", nil, "", http.StatusBadRequest) return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.members_too_few.error", nil, "", http.StatusBadRequest)
} else if len(*data.Members) > model.ChannelGroupMaxUsers { } else if len(*data.Members) > model.ChannelGroupMaxUsers {
@@ -508,12 +516,20 @@ func ValidateDirectChannelImportData(data *DirectChannelImportData) *model.AppEr
if data.FavoritedBy != nil { if data.FavoritedBy != nil {
for _, favoriter := range *data.FavoritedBy { for _, favoriter := range *data.FavoritedBy {
found := false found := false
for _, member := range *data.Members { for _, member := range data.Participants {
if favoriter == member { if favoriter == *member.Username {
found = true found = true
break break
} }
} }
if data.Members != nil {
for _, member := range *data.Members {
if favoriter == member {
found = true
break
}
}
}
if !found { if !found {
return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.unknown_favoriter.error", map[string]any{"Username": favoriter}, "", http.StatusBadRequest) return model.NewAppError("BulkImport", "app.import.validate_direct_channel_import_data.unknown_favoriter.error", map[string]any{"Username": favoriter}, "", http.StatusBadRequest)
} }

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@@ -4088,7 +4088,7 @@ func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId s
channelIds = append(channelIds, channel.Id) channelIds = append(channelIds, channel.Id)
} }
query = s.getQueryBuilder(). query = s.getQueryBuilder().
Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MentionCount, MentionCountRoot, COALESCE(UrgentMentionCount, 0) UrgentMentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest"). Select("u.Username as Username, ChannelId, UserId, cm.Roles as Roles, LastViewedAt, MsgCount, MsgCountRoot, MentionCount, MentionCountRoot, COALESCE(UrgentMentionCount, 0) UrgentMentionCount, cm.NotifyProps as NotifyProps, LastUpdateAt, SchemeUser, SchemeAdmin, (SchemeGuest IS NOT NULL AND SchemeGuest) as SchemeGuest").
From("ChannelMembers cm"). From("ChannelMembers cm").
Join("Users u ON ( u.Id = cm.UserId )"). Join("Users u ON ( u.Id = cm.UserId )").
Where(sq.Eq{"cm.ChannelId": channelIds}) Where(sq.Eq{"cm.ChannelId": channelIds})
@@ -4106,12 +4106,11 @@ func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId s
// Populate each channel with its members // Populate each channel with its members
dmChannelsMap := make(map[string]*model.DirectChannelForExport) dmChannelsMap := make(map[string]*model.DirectChannelForExport)
for _, channel := range directChannelsForExport { for _, channel := range directChannelsForExport {
channel.Members = &[]string{} channel.Members = []*model.ChannelMemberForExport{}
dmChannelsMap[channel.Id] = channel dmChannelsMap[channel.Id] = channel
} }
for _, member := range channelMembers { for _, member := range channelMembers {
members := dmChannelsMap[member.ChannelId].Members dmChannelsMap[member.ChannelId].Members = append(dmChannelsMap[member.ChannelId].Members, member)
*members = append(*members, member.Username)
} }
return directChannelsForExport, nil return directChannelsForExport, nil

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

@@ -7852,7 +7852,7 @@ func testChannelStoreExportAllDirectChannelsDeletedChannel(t *testing.T, rctx re
d1, nErr = ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26), true) d1, nErr = ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26), true)
assert.NoError(t, nErr) assert.NoError(t, nErr)
assert.Len(t, d1, 1) assert.Len(t, d1, 1)
assert.Len(t, *d1[0].Members, 2) assert.Len(t, d1[0].Members, 2)
// Manually truncate Channels table until testlib can handle cleanups // Manually truncate Channels table until testlib can handle cleanups
s.GetMasterX().Exec("TRUNCATE Channels") s.GetMasterX().Exec("TRUNCATE Channels")

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

@@ -832,7 +832,17 @@ func (v *Validator) validateDirectChannel(info ImportFileInfo, line imports.Line
} }
} }
if data.Members != nil { if data.Participants != nil {
for i, member := range data.Participants {
if _, ok := v.users[*member.Username]; !ok {
return &ImportValidationError{
ImportFileInfo: info,
FieldName: fmt.Sprintf("direct_channel.members[%d]", i),
Err: fmt.Errorf("reference to unknown user %q", *member.Username),
}
}
}
} else if data.Members != nil {
for i, member := range *data.Members { for i, member := range *data.Members {
if _, ok := v.users[member]; !ok { if _, ok := v.users[member]; !ok {
return &ImportValidationError{ return &ImportValidationError{

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

@@ -394,10 +394,17 @@ func createPost(team string, channel string, allUsers []string, createAt int64)
func createDirectChannel(members []string) imports.LineImportData { func createDirectChannel(members []string) imports.LineImportData {
header := fake.Sentence() header := fake.Sentence()
var p []*imports.DirectChannelMemberImportData
for _, m := range members {
p = append(p, &imports.DirectChannelMemberImportData{
Username: model.NewString(m),
})
}
channel := imports.DirectChannelImportData{ channel := imports.DirectChannelImportData{
Members: &members, Participants: p,
Header: &header, Header: &header,
} }
return imports.LineImportData{ return imports.LineImportData{
Type: "direct_channel", Type: "direct_channel",

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

@@ -5174,6 +5174,14 @@
"id": "app.import.import_direct_channel.create_group_channel.error", "id": "app.import.import_direct_channel.create_group_channel.error",
"translation": "Failed to create group channel" "translation": "Failed to create group channel"
}, },
{
"id": "app.import.import_direct_channel.get_channel_members.error",
"translation": "Failed to get channel members for direct channel"
},
{
"id": "app.import.import_direct_channel.no_members.error",
"translation": "There are no members for the direct channel"
},
{ {
"id": "app.import.import_direct_channel.update_header_failed.error", "id": "app.import.import_direct_channel.update_header_failed.error",
"translation": "Failed to update direct channel header" "translation": "Failed to update direct channel header"

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

@@ -122,7 +122,7 @@ type ChannelForExport struct {
type DirectChannelForExport struct { type DirectChannelForExport struct {
Channel Channel
Members *[]string Members []*ChannelMemberForExport
} }
type ChannelModeration struct { type ChannelModeration struct {