diff --git a/app/export.go b/app/export.go index 1921a748e4..bf734ffc2b 100644 --- a/app/export.go +++ b/app/export.go @@ -184,11 +184,6 @@ func (a *App) ExportAllUsers(writer io.Writer) *model.AppError { for _, user := range users { afterId = user.Id - // Skip deleted. - if user.DeleteAt != 0 { - continue - } - // Gathering here the exportable preferences to pass them on to ImportLineFromUser exportedPrefs := make(map[string]*string) allPrefs, err := a.GetPreferencesForUser(user.Id) diff --git a/app/export_converters.go b/app/export_converters.go index a25b6aec98..0a57636691 100644 --- a/app/export_converters.go +++ b/app/export_converters.go @@ -68,6 +68,7 @@ func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *Lin ChannelDisplayMode: exportedPrefs["ChannelDisplayMode"], TutorialStep: exportedPrefs["TutorialStep"], EmailInterval: exportedPrefs["EmailInterval"], + DeleteAt: &user.DeleteAt, }, } } diff --git a/app/export_test.go b/app/export_test.go index df1ed88a3b..6f852e7c89 100644 --- a/app/export_test.go +++ b/app/export_test.go @@ -170,8 +170,13 @@ func TestExportAllUsers(t *testing.T) { th1 := Setup(t).InitBasic() defer th1.TearDown() + // Adding a user and deactivating it to check whether it gets included in bulk export + user := th1.CreateUser() + _, err := th1.App.UpdateActive(user, false) + require.Nil(t, err) + var b bytes.Buffer - err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir") + err = th1.App.BulkExport(&b, "somefile", "somePath", "someDir") require.Nil(t, err) th2 := Setup(t) @@ -192,4 +197,20 @@ func TestExportAllUsers(t *testing.T) { assert.Nil(t, err) assert.Equal(t, len(users1), len(users2)) assert.ElementsMatch(t, users1, users2) + + // Checking whether deactivated users were included in bulk export + deletedUsers1, err := th1.App.GetUsers(&model.UserGetOptions{ + Inactive: true, + Page: 0, + PerPage: 10, + }) + assert.Nil(t, err) + deletedUsers2, err := th1.App.GetUsers(&model.UserGetOptions{ + Inactive: true, + Page: 0, + PerPage: 10, + }) + assert.Nil(t, err) + assert.Equal(t, len(deletedUsers1), len(deletedUsers2)) + assert.ElementsMatch(t, deletedUsers1, deletedUsers2) } diff --git a/app/import_functions.go b/app/import_functions.go index 9fbfa42aeb..f611a7838e 100644 --- a/app/import_functions.go +++ b/app/import_functions.go @@ -369,6 +369,13 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { } } + if data.DeleteAt != nil { + if user.DeleteAt != *data.DeleteAt { + user.DeleteAt = *data.DeleteAt + hasUserChanged = true + } + } + var roles string if data.Roles != nil { if user.Roles != *data.Roles { diff --git a/app/import_functions_test.go b/app/import_functions_test.go index 1be40be5f1..248940367e 100644 --- a/app/import_functions_test.go +++ b/app/import_functions_test.go @@ -1300,6 +1300,51 @@ func TestImportImportUser(t *testing.T) { assert.True(t, channelMember.SchemeUser) assert.Equal(t, "", channelMember.ExplicitRoles) + + // Test importing deleted user with a valid team & valid channel name in apply mode. + username = model.NewId() + deleteAt := model.GetMillis() + deletedUserData := &UserImportData{ + Username: &username, + DeleteAt: &deleteAt, + Email: ptrStr(model.NewId() + "@example.com"), + Teams: &[]UserTeamImportData{ + { + Name: &team.Name, + Roles: ptrStr("team_user"), + Channels: &[]UserChannelImportData{ + { + Name: &channel.Name, + Roles: ptrStr("channel_user"), + }, + }, + }, + }, + } + err = th.App.ImportUser(deletedUserData, false) + assert.Nil(t, err) + + user, err = th.App.GetUserByUsername(*deletedUserData.Username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + teamMember, err = th.App.GetTeamMember(team.Id, user.Id) + if err != nil { + t.Fatalf("Failed to get the team member") + } + + assert.True(t, teamMember.SchemeUser) + assert.Equal(t, "", teamMember.ExplicitRoles) + + channelMember, err = th.App.GetChannelMember(channel.Id, user.Id) + if err != nil { + t.Fatalf("Failed to get the channel member") + } + + assert.True(t, channelMember.SchemeUser) + assert.Equal(t, "", channelMember.ExplicitRoles) + } func TestImportUserDefaultNotifyProps(t *testing.T) { diff --git a/app/import_test.go b/app/import_test.go index b5f50c8843..21382b5679 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -182,12 +182,13 @@ func TestImportBulkImport(t *testing.T) { {"type": "channel", "channel": {"type": "O", "display_name": "xr6m6udffngark2uekvr3hoeny", "team": "` + teamName + `", "name": "` + channelName + `"}} {"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}} {"type": "user", "user": {"username": "` + username2 + `", "email": "` + username2 + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme2 + `", "channels": [{"name": "` + channelName + `"}]}]}} -{"type": "user", "user": {"username": "` + username3 + `", "email": "` + username3 + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}]}]}} +{"type": "user", "user": {"username": "` + username3 + `", "email": "` + username3 + `@example.com", "teams": [{"name": "` + teamName + `", "channels": [{"name": "` + channelName + `"}], "delete_at": 123456789016}]}} {"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012, "attachments":[{"path": "` + testImage + `"}]}} +{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username3 + `", "message": "Hey Everyone!", "create_at": 123456789013, "attachments":[{"path": "` + testImage + `"}]}} {"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `"]}} {"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username2 + `", "` + username3 + `"]}} -{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `"], "user": "` + username + `", "message": "Hello Direct Channel", "create_at": 123456789013}} -{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789014}} +{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `"], "user": "` + username + `", "message": "Hello Direct Channel", "create_at": 123456789014}} +{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username2 + `", "` + username3 + `"], "user": "` + username + `", "message": "Hello Group Channel", "create_at": 123456789015}} {"type": "emoji", "emoji": {"name": "` + emojiName + `", "image": "` + testImage + `"}}` if err, line := th.App.BulkImport(strings.NewReader(data1), false, 2); err != nil || line != 0 { diff --git a/app/import_types.go b/app/import_types.go index ce15a37448..d286d925bb 100644 --- a/app/import_types.go +++ b/app/import_types.go @@ -55,6 +55,7 @@ type UserImportData struct { UseMarkdownPreview *string `json:"feature_enabled_markdown_preview,omitempty"` UseFormatting *string `json:"formatting,omitempty"` ShowUnreadSection *string `json:"show_unread_section,omitempty"` + DeleteAt *int64 `json:"delete_at,omitempty"` Teams *[]UserTeamImportData `json:"teams,omitempty"` diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index 81a112b314..9359e3061a 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -1327,7 +1327,6 @@ func (s *SqlPostStore) GetParentsForExportAfter(limit int, afterId string) store AND p1.DeleteAt = 0 AND Channels.DeleteAt = 0 AND Teams.DeleteAt = 0 - AND Users.DeleteAt = 0 ORDER BY p1.Id LIMIT @@ -1356,7 +1355,6 @@ func (s *SqlPostStore) GetRepliesForExport(parentId string) store.StoreChannel { WHERE Posts.ParentId = :ParentId AND Posts.DeleteAt = 0 - AND Users.DeleteAt = 0 ORDER BY Posts.Id`, map[string]interface{}{"ParentId": parentId}) diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index d0fd363bde..bac6ba3173 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -1941,4 +1941,20 @@ func testPostStoreGetRepliesForExport(t *testing.T, ss store.Store) { assert.Equal(t, reply1.Id, p2.Id) assert.Equal(t, reply1.Message, p2.Message) assert.Equal(t, reply1.Username, u1.Username) + + // Checking whether replies by deleted user are exported + u1.DeleteAt = 1002 + store.Must(ss.User().Update(&u1, false)) + + r1 = <-ss.Post().GetRepliesForExport(p1.Id) + assert.Nil(t, r1.Err) + + d1 = r1.Data.([]*model.ReplyForExport) + assert.Len(t, d1, 1) + + reply1 = d1[0] + assert.Equal(t, reply1.Id, p2.Id) + assert.Equal(t, reply1.Message, p2.Message) + assert.Equal(t, reply1.Username, u1.Username) + }