[MM-38375] Fix processing bulk import with attachments (#18352)
* Fix improper attachments in replies * Fix import data path * Improve errors * Fix importing attachments directly from zip file * Add some test cases to cover error paths
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
c4a25ff339
Коммит
0c84885132
@@ -463,7 +463,7 @@ func (a *App) buildPostReplies(postID string, withAttachments bool) ([]ReplyImpo
|
|||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
return nil, nil, appErr
|
return nil, nil, appErr
|
||||||
}
|
}
|
||||||
replyImportObject.Attachments = &attachments
|
replyImportObject.Attachments = &postAttachments
|
||||||
if withAttachments && len(postAttachments) > 0 {
|
if withAttachments && len(postAttachments) > 0 {
|
||||||
attachments = append(attachments, postAttachments...)
|
attachments = append(attachments, postAttachments...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -638,3 +639,67 @@ func TestBulkExport(t *testing.T) {
|
|||||||
appErr, _ = th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, 1, filepath.Join(dir, "data"))
|
appErr, _ = th.App.BulkImportWithPath(th.Context, jsonFile, nil, false, 1, filepath.Join(dir, "data"))
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildPostReplies(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
createPostWithAttachments := func(th *TestHelper, n int, rootID string) *model.Post {
|
||||||
|
var fileIDs []string
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
info, err := th.App.Srv().Store.FileInfo().Save(&model.FileInfo{
|
||||||
|
CreatorId: th.BasicUser.Id,
|
||||||
|
Name: fmt.Sprintf("file%d", i),
|
||||||
|
Path: fmt.Sprintf("/data/file%d", i),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
fileIDs = append(fileIDs, info.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
post, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, RootId: rootID, FileIds: fileIDs}, th.BasicChannel, false, true)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("basic post", func(t *testing.T) {
|
||||||
|
data, attachments, err := th.App.buildPostReplies(th.BasicPost.Id, true)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Empty(t, data)
|
||||||
|
require.Empty(t, attachments)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("root post with attachments and no replies", func(t *testing.T) {
|
||||||
|
post := createPostWithAttachments(th, 5, "")
|
||||||
|
data, attachments, err := th.App.buildPostReplies(post.Id, true)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Empty(t, data)
|
||||||
|
require.Empty(t, attachments)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("root post with attachments and a reply", func(t *testing.T) {
|
||||||
|
post := createPostWithAttachments(th, 5, "")
|
||||||
|
createPostWithAttachments(th, 0, post.Id)
|
||||||
|
data, attachments, err := th.App.buildPostReplies(post.Id, true)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Len(t, data, 1)
|
||||||
|
require.Empty(t, attachments)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("root post with attachments and multiple replies with attachments", func(t *testing.T) {
|
||||||
|
post := createPostWithAttachments(th, 5, "")
|
||||||
|
reply1 := createPostWithAttachments(th, 2, post.Id)
|
||||||
|
reply2 := createPostWithAttachments(th, 3, post.Id)
|
||||||
|
data, attachments, err := th.App.buildPostReplies(post.Id, true)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Len(t, data, 2)
|
||||||
|
require.Len(t, attachments, 5)
|
||||||
|
if reply1.Id < reply2.Id {
|
||||||
|
require.Len(t, *data[0].Attachments, 2)
|
||||||
|
require.Len(t, *data[1].Attachments, 3)
|
||||||
|
} else {
|
||||||
|
require.Len(t, *data[1].Attachments, 2)
|
||||||
|
require.Len(t, *data[0].Attachments, 3)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package imaging
|
package imaging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@@ -59,17 +60,17 @@ func MakeImageUpright(img image.Image, orientation int) image.Image {
|
|||||||
func GetImageOrientation(input io.Reader) (int, error) {
|
func GetImageOrientation(input io.Reader) (int, error) {
|
||||||
exifData, err := exif.Decode(input)
|
exifData, err := exif.Decode(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Upright, err
|
return Upright, fmt.Errorf("failed to decode exif data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, err := exifData.Get("Orientation")
|
tag, err := exifData.Get("Orientation")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Upright, err
|
return Upright, fmt.Errorf("failed to get orientation field from exif data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
orientation, err := tag.Int(0)
|
orientation, err := tag.Int(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Upright, err
|
return Upright, fmt.Errorf("failed to get value from exif tag: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return orientation, nil
|
return orientation, nil
|
||||||
|
|||||||
@@ -38,44 +38,74 @@ func stopOnError(err LineImportWorkerError) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rewriteAttachmentPaths(files *[]AttachmentImportData, basePath string) {
|
func processAttachmentPaths(files *[]AttachmentImportData, basePath string, filesMap map[string]*zip.File) error {
|
||||||
if files == nil {
|
if files == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
for _, f := range *files {
|
var ok bool
|
||||||
|
for i, f := range *files {
|
||||||
if f.Path != nil {
|
if f.Path != nil {
|
||||||
*f.Path = filepath.Join(basePath, *f.Path)
|
path := filepath.Join(basePath, *f.Path)
|
||||||
|
*f.Path = path
|
||||||
|
if len(filesMap) > 0 {
|
||||||
|
if (*files)[i].Data, ok = filesMap[path]; !ok {
|
||||||
|
return fmt.Errorf("attachment %q not found in map", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rewriteFilePaths(line *LineImportData, basePath string) {
|
func processAttachments(line *LineImportData, basePath string, filesMap map[string]*zip.File) error {
|
||||||
|
var ok bool
|
||||||
switch line.Type {
|
switch line.Type {
|
||||||
case "post", "direct_post":
|
case "post", "direct_post":
|
||||||
var replies []ReplyImportData
|
var replies []ReplyImportData
|
||||||
if line.Type == "direct_post" {
|
if line.Type == "direct_post" {
|
||||||
rewriteAttachmentPaths(line.DirectPost.Attachments, basePath)
|
if err := processAttachmentPaths(line.DirectPost.Attachments, basePath, filesMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if line.DirectPost.Replies != nil {
|
if line.DirectPost.Replies != nil {
|
||||||
replies = *line.DirectPost.Replies
|
replies = *line.DirectPost.Replies
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rewriteAttachmentPaths(line.Post.Attachments, basePath)
|
if err := processAttachmentPaths(line.Post.Attachments, basePath, filesMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if line.Post.Replies != nil {
|
if line.Post.Replies != nil {
|
||||||
replies = *line.Post.Replies
|
replies = *line.Post.Replies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, reply := range replies {
|
for _, reply := range replies {
|
||||||
rewriteAttachmentPaths(reply.Attachments, basePath)
|
if err := processAttachmentPaths(reply.Attachments, basePath, filesMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "user":
|
case "user":
|
||||||
if line.User.ProfileImage != nil {
|
if line.User.ProfileImage != nil {
|
||||||
*line.User.ProfileImage = filepath.Join(basePath, *line.User.ProfileImage)
|
path := filepath.Join(basePath, *line.User.ProfileImage)
|
||||||
|
*line.User.ProfileImage = path
|
||||||
|
if len(filesMap) > 0 {
|
||||||
|
if line.User.ProfileImageData, ok = filesMap[path]; !ok {
|
||||||
|
return fmt.Errorf("attachment %q not found in map", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "emoji":
|
case "emoji":
|
||||||
if line.Emoji.Image != nil {
|
if line.Emoji.Image != nil {
|
||||||
*line.Emoji.Image = filepath.Join(basePath, *line.Emoji.Image)
|
path := filepath.Join(basePath, *line.Emoji.Image)
|
||||||
|
*line.Emoji.Image = path
|
||||||
|
if len(filesMap) > 0 {
|
||||||
|
if line.Emoji.Data, ok = filesMap[path]; !ok {
|
||||||
|
return fmt.Errorf("attachment %q not found in map", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) bulkImportWorker(c *request.Context, dryRun bool, wg *sync.WaitGroup, lines <-chan LineImportWorkerData, errors chan<- LineImportWorkerError) {
|
func (a *App) bulkImportWorker(c *request.Context, dryRun bool, wg *sync.WaitGroup, lines <-chan LineImportWorkerData, errors chan<- LineImportWorkerError) {
|
||||||
@@ -169,18 +199,8 @@ func (a *App) bulkImport(c *request.Context, jsonlReader io.Reader, attachmentsR
|
|||||||
return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, err.Error(), http.StatusBadRequest), lineNumber
|
return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, err.Error(), http.StatusBadRequest), lineNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(attachedFiles) > 0 && line.Post != nil && line.Post.Attachments != nil {
|
if err := processAttachments(&line, importPath, attachedFiles); err != nil {
|
||||||
for i, attachment := range *line.Post.Attachments {
|
return model.NewAppError("BulkImport", "app.import.bulk_import.process_attachments.error", nil, err.Error(), http.StatusBadRequest), lineNumber
|
||||||
var ok bool
|
|
||||||
path := *attachment.Path
|
|
||||||
if (*line.Post.Attachments)[i].Data, ok = attachedFiles[path]; !ok {
|
|
||||||
return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, fmt.Sprintf("attachment '%s' not found in map", path), http.StatusBadRequest), lineNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if importPath != "" {
|
|
||||||
rewriteFilePaths(&line, importPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if lineNumber == 1 {
|
if lineNumber == 1 {
|
||||||
|
|||||||
@@ -565,13 +565,22 @@ func (a *App) importUser(data *UserImportData, dryRun bool) *model.AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if data.ProfileImage != nil {
|
if data.ProfileImage != nil {
|
||||||
file, err := os.Open(*data.ProfileImage)
|
var file io.ReadCloser
|
||||||
|
var err error
|
||||||
|
if data.ProfileImageData != nil {
|
||||||
|
file, err = data.ProfileImageData.Open()
|
||||||
|
} else {
|
||||||
|
file, err = os.Open(*data.ProfileImage)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Warn("Unable to open the profile image.", mlog.Err(err))
|
mlog.Warn("Unable to open the profile image.", mlog.Err(err))
|
||||||
}
|
} else {
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
if err == nil {
|
if limitErr := checkImageLimits(file, *a.Config().FileSettings.MaxImageResolution); limitErr != nil {
|
||||||
if err := a.SetProfileImageFromMultiPartFile(savedUser.Id, file); err != nil {
|
return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.check_image_limits.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
if err := a.SetProfileImageFromFile(savedUser.Id, file); err != nil {
|
||||||
mlog.Warn("Unable to set the profile image from a file.", mlog.Err(err))
|
mlog.Warn("Unable to set the profile image from a file.", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1804,7 +1813,12 @@ func (a *App) importEmoji(data *EmojiImportData, dryRun bool) *model.AppError {
|
|||||||
emoji.PreSave()
|
emoji.PreSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(*data.Image)
|
var file io.ReadCloser
|
||||||
|
if data.Data != nil {
|
||||||
|
file, err = data.Data.Open()
|
||||||
|
} else {
|
||||||
|
file, err = os.Open(*data.Image)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("BulkImport", "app.import.emoji.bad_file.error", map[string]interface{}{"EmojiName": *data.Name}, "", http.StatusBadRequest)
|
return model.NewAppError("BulkImport", "app.import.emoji.bad_file.error", map[string]interface{}{"EmojiName": *data.Name}, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -278,7 +280,7 @@ func AssertFileIdsInPost(files []*model.FileInfo, th *TestHelper, t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteFilePaths(t *testing.T) {
|
func TestProcessAttachments(t *testing.T) {
|
||||||
genAttachments := func() *[]AttachmentImportData {
|
genAttachments := func() *[]AttachmentImportData {
|
||||||
return &[]AttachmentImportData{
|
return &[]AttachmentImportData{
|
||||||
{
|
{
|
||||||
@@ -327,9 +329,11 @@ func TestRewriteFilePaths(t *testing.T) {
|
|||||||
Path: model.NewString("somedir/file.jpg"),
|
Path: model.NewString("somedir/file.jpg"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
rewriteFilePaths(&line, "")
|
err := processAttachments(&line, "", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, line.Post.Attachments)
|
require.Equal(t, expected, line.Post.Attachments)
|
||||||
rewriteFilePaths(&line2, "")
|
err = processAttachments(&line2, "", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, line2.DirectPost.Attachments)
|
require.Equal(t, expected, line2.DirectPost.Attachments)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -344,27 +348,81 @@ func TestRewriteFilePaths(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("post attachments", func(t *testing.T) {
|
t.Run("post attachments", func(t *testing.T) {
|
||||||
rewriteFilePaths(&line, "/tmp")
|
err := processAttachments(&line, "/tmp", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, line.Post.Attachments)
|
require.Equal(t, expected, line.Post.Attachments)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("direct post attachments", func(t *testing.T) {
|
t.Run("direct post attachments", func(t *testing.T) {
|
||||||
rewriteFilePaths(&line2, "/tmp")
|
err := processAttachments(&line2, "/tmp", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, line2.DirectPost.Attachments)
|
require.Equal(t, expected, line2.DirectPost.Attachments)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("profile image", func(t *testing.T) {
|
t.Run("profile image", func(t *testing.T) {
|
||||||
expected := "/tmp/profile.jpg"
|
expected := "/tmp/profile.jpg"
|
||||||
rewriteFilePaths(&userLine, "/tmp")
|
err := processAttachments(&userLine, "/tmp", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, *userLine.User.ProfileImage)
|
require.Equal(t, expected, *userLine.User.ProfileImage)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("emoji", func(t *testing.T) {
|
t.Run("emoji", func(t *testing.T) {
|
||||||
expected := "/tmp/emoji.png"
|
expected := "/tmp/emoji.png"
|
||||||
rewriteFilePaths(&emojiLine, "/tmp")
|
err := processAttachments(&emojiLine, "/tmp", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, *emojiLine.Emoji.Image)
|
require.Equal(t, expected, *emojiLine.Emoji.Image)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("with filesMap", func(t *testing.T) {
|
||||||
|
t.Run("post attachments", func(t *testing.T) {
|
||||||
|
filesMap := map[string]*zip.File{
|
||||||
|
"/tmp/file.jpg": nil,
|
||||||
|
}
|
||||||
|
err := processAttachments(&line, "", filesMap)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
filesMap["/tmp/somedir/file.jpg"] = nil
|
||||||
|
err = processAttachments(&line, "", filesMap)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("direct post attachments", func(t *testing.T) {
|
||||||
|
filesMap := map[string]*zip.File{
|
||||||
|
"/tmp/file.jpg": nil,
|
||||||
|
}
|
||||||
|
err := processAttachments(&line2, "", filesMap)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
filesMap["/tmp/somedir/file.jpg"] = nil
|
||||||
|
err = processAttachments(&line2, "", filesMap)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("profile image", func(t *testing.T) {
|
||||||
|
filesMap := map[string]*zip.File{
|
||||||
|
"/tmp/file.jpg": nil,
|
||||||
|
}
|
||||||
|
err := processAttachments(&userLine, "", filesMap)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
filesMap["/tmp/profile.jpg"] = nil
|
||||||
|
err = processAttachments(&userLine, "", filesMap)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("emoji", func(t *testing.T) {
|
||||||
|
filesMap := map[string]*zip.File{
|
||||||
|
"/tmp/file.jpg": nil,
|
||||||
|
}
|
||||||
|
err := processAttachments(&emojiLine, "", filesMap)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
filesMap["/tmp/emoji.png"] = nil
|
||||||
|
err = processAttachments(&emojiLine, "", filesMap)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkBulkImport(b *testing.B) {
|
func BenchmarkBulkImport(b *testing.B) {
|
||||||
@@ -398,3 +456,45 @@ func BenchmarkBulkImport(b *testing.B) {
|
|||||||
}
|
}
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImportBulkImportWithAttachments(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
testsDir, _ := fileutils.FindDir("tests")
|
||||||
|
|
||||||
|
importFile, err := os.Open(testsDir + "/export_test.zip")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer importFile.Close()
|
||||||
|
|
||||||
|
info, err := importFile.Stat()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
importZipReader, err := zip.NewReader(importFile, info.Size())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, importZipReader)
|
||||||
|
|
||||||
|
var jsonFile io.ReadCloser
|
||||||
|
for _, f := range importZipReader.File {
|
||||||
|
if filepath.Ext(f.Name) != ".jsonl" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFile, err = f.Open()
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer jsonFile.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
require.NotNil(t, jsonFile)
|
||||||
|
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.MaxUsersPerTeam = model.NewInt(1000) })
|
||||||
|
|
||||||
|
appErr, _ := th.App.BulkImportWithPath(th.Context, jsonFile, importZipReader, false, 1, ExportDataDir)
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
adminUser, appErr := th.App.GetUserByUsername("sysadmin")
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
files := GetAttachments(adminUser.Id, th, t)
|
||||||
|
require.Len(t, files, 11)
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,22 +44,23 @@ type ChannelImportData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserImportData struct {
|
type UserImportData struct {
|
||||||
ProfileImage *string `json:"profile_image,omitempty"`
|
ProfileImage *string `json:"profile_image,omitempty"`
|
||||||
Username *string `json:"username"`
|
ProfileImageData *zip.File `json:"-"`
|
||||||
Email *string `json:"email"`
|
Username *string `json:"username"`
|
||||||
AuthService *string `json:"auth_service"`
|
Email *string `json:"email"`
|
||||||
AuthData *string `json:"auth_data,omitempty"`
|
AuthService *string `json:"auth_service"`
|
||||||
Password *string `json:"password,omitempty"`
|
AuthData *string `json:"auth_data,omitempty"`
|
||||||
Nickname *string `json:"nickname"`
|
Password *string `json:"password,omitempty"`
|
||||||
FirstName *string `json:"first_name"`
|
Nickname *string `json:"nickname"`
|
||||||
LastName *string `json:"last_name"`
|
FirstName *string `json:"first_name"`
|
||||||
Position *string `json:"position"`
|
LastName *string `json:"last_name"`
|
||||||
Roles *string `json:"roles"`
|
Position *string `json:"position"`
|
||||||
Locale *string `json:"locale"`
|
Roles *string `json:"roles"`
|
||||||
UseMarkdownPreview *string `json:"feature_enabled_markdown_preview,omitempty"`
|
Locale *string `json:"locale"`
|
||||||
UseFormatting *string `json:"formatting,omitempty"`
|
UseMarkdownPreview *string `json:"feature_enabled_markdown_preview,omitempty"`
|
||||||
ShowUnreadSection *string `json:"show_unread_section,omitempty"`
|
UseFormatting *string `json:"formatting,omitempty"`
|
||||||
DeleteAt *int64 `json:"delete_at,omitempty"`
|
ShowUnreadSection *string `json:"show_unread_section,omitempty"`
|
||||||
|
DeleteAt *int64 `json:"delete_at,omitempty"`
|
||||||
|
|
||||||
Teams *[]UserTeamImportData `json:"teams,omitempty"`
|
Teams *[]UserTeamImportData `json:"teams,omitempty"`
|
||||||
|
|
||||||
@@ -109,8 +110,9 @@ type UserChannelNotifyPropsImportData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EmojiImportData struct {
|
type EmojiImportData struct {
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Image *string `json:"image"`
|
Image *string `json:"image"`
|
||||||
|
Data *zip.File `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReactionImportData struct {
|
type ReactionImportData struct {
|
||||||
|
|||||||
@@ -4867,6 +4867,10 @@
|
|||||||
"id": "app.import.bulk_import.json_decode.error",
|
"id": "app.import.bulk_import.json_decode.error",
|
||||||
"translation": "JSON decode of line failed."
|
"translation": "JSON decode of line failed."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "app.import.bulk_import.process_attachments.error",
|
||||||
|
"translation": "Error while processing bulk import attachments."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "app.import.bulk_import.unsupported_version.error",
|
"id": "app.import.bulk_import.unsupported_version.error",
|
||||||
"translation": "Incorrect or missing version in the data import file. Make sure version is the first object in your import file and try again."
|
"translation": "Incorrect or missing version in the data import file. Make sure version is the first object in your import file and try again."
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func (w *ImportProcessWorker) doJob(job *model.Job) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do the actual import.
|
// do the actual import.
|
||||||
appErr, lineNumber := w.app.BulkImport(w.appContext, jsonFile, importZipReader, false, runtime.NumCPU())
|
appErr, lineNumber := w.app.BulkImportWithPath(w.appContext, jsonFile, importZipReader, false, runtime.NumCPU(), app.ExportDataDir)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
job.Data["line_number"] = strconv.Itoa(lineNumber)
|
job.Data["line_number"] = strconv.Itoa(lineNumber)
|
||||||
w.setJobError(job, appErr)
|
w.setJobError(job, appErr)
|
||||||
|
|||||||
Двоичные данные
tests/export_test.zip
Обычный файл
Двоичные данные
tests/export_test.zip
Обычный файл
Двоичный файл не отображается.
Ссылка в новой задаче
Block a user