From 6990d052d5e95295e729aae28a0d30bfdcb98573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 11 Jan 2018 16:57:47 +0100 Subject: [XYZ-6] Add sampledata platform command (#8027) * Add fake dependency * [XYZ-6] Add sampledata platform command * Creating EMOJI_NAME_MAX_LENGTH as a constant and using it where needed --- app/import.go | 236 ++++++++++++++++++++++++++++++++++++--- app/import_test.go | 320 +++++++++++++++++++++++++++++++++++++++++++++++++---- app/user.go | 3 + 3 files changed, 527 insertions(+), 32 deletions(-) (limited to 'app') diff --git a/app/import.go b/app/import.go index 850e9c43d..6291794b0 100644 --- a/app/import.go +++ b/app/import.go @@ -9,6 +9,7 @@ import ( "encoding/json" "io" "net/http" + "os" "regexp" "strings" "sync" @@ -53,17 +54,18 @@ type ChannelImportData struct { } type UserImportData struct { - Username *string `json:"username"` - Email *string `json:"email"` - AuthService *string `json:"auth_service"` - AuthData *string `json:"auth_data"` - Password *string `json:"password"` - Nickname *string `json:"nickname"` - FirstName *string `json:"first_name"` - LastName *string `json:"last_name"` - Position *string `json:"position"` - Roles *string `json:"roles"` - Locale *string `json:"locale"` + ProfileImage *string `json:"profile_image"` + Username *string `json:"username"` + Email *string `json:"email"` + AuthService *string `json:"auth_service"` + AuthData *string `json:"auth_data"` + Password *string `json:"password"` + Nickname *string `json:"nickname"` + FirstName *string `json:"first_name"` + LastName *string `json:"last_name"` + Position *string `json:"position"` + Roles *string `json:"roles"` + Locale *string `json:"locale"` Teams *[]UserTeamImportData `json:"teams"` @@ -111,6 +113,22 @@ type UserChannelNotifyPropsImportData struct { MarkUnread *string `json:"mark_unread"` } +type ReactionImportData struct { + User *string `json:"user"` + CreateAt *int64 `json:"create_at"` + EmojiName *string `json:"emoji_name"` +} + +type ReplyImportData struct { + User *string `json:"user"` + + Message *string `json:"message"` + CreateAt *int64 `json:"create_at"` + + FlaggedBy *[]string `json:"flagged_by"` + Reactions *[]ReactionImportData `json:"reactions"` +} + type PostImportData struct { Team *string `json:"team"` Channel *string `json:"channel"` @@ -119,7 +137,9 @@ type PostImportData struct { Message *string `json:"message"` CreateAt *int64 `json:"create_at"` - FlaggedBy *[]string `json:"flagged_by"` + FlaggedBy *[]string `json:"flagged_by"` + Reactions *[]ReactionImportData `json:"reactions"` + Replies *[]ReplyImportData `json:"replies"` } type DirectChannelImportData struct { @@ -136,7 +156,9 @@ type DirectPostImportData struct { Message *string `json:"message"` CreateAt *int64 `json:"create_at"` - FlaggedBy *[]string `json:"flagged_by"` + FlaggedBy *[]string `json:"flagged_by"` + Reactions *[]ReactionImportData `json:"reactions"` + Replies *[]ReplyImportData `json:"replies"` } type LineImportWorkerData struct { @@ -690,6 +712,16 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { savedUser = user } + if data.ProfileImage != nil { + file, err := os.Open(*data.ProfileImage) + if err != nil { + l4g.Error(utils.T("api.import.import_user.profile_image.error"), err) + } + if err := a.SetProfileImageFromFile(savedUser.Id, file); err != nil { + l4g.Error(utils.T("api.import.import_user.profile_image.error"), err) + } + } + // Preferences. var preferences model.Preferences @@ -869,6 +901,11 @@ func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember } func validateUserImportData(data *UserImportData) *model.AppError { + if data.ProfileImage != nil { + if _, err := os.Stat(*data.ProfileImage); os.IsNotExist(err) { + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.profile_image.error", nil, "", http.StatusBadRequest) + } + } if data.Username == nil { return model.NewAppError("BulkImport", "app.import.validate_user_import_data.username_missing.error", nil, "", http.StatusBadRequest) @@ -1019,6 +1056,79 @@ func validateUserChannelsImportData(data *[]UserChannelImportData) *model.AppErr return nil } +func (a *App) ImportReaction(data *ReactionImportData, post *model.Post, dryRun bool) *model.AppError { + if err := validateReactionImportData(data, post.CreateAt); err != nil { + return err + } + + var user *model.User + if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, "", http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + reaction := &model.Reaction{ + UserId: user.Id, + PostId: post.Id, + EmojiName: *data.EmojiName, + CreateAt: *data.CreateAt, + } + if result := <-a.Srv.Store.Reaction().Save(reaction); result.Err != nil { + return result.Err + } + return nil +} + +func (a *App) ImportReply(data *ReplyImportData, post *model.Post, dryRun bool) *model.AppError { + if err := validateReplyImportData(data, post.CreateAt); err != nil { + return err + } + + var user *model.User + if result := <-a.Srv.Store.User().GetByUsername(*data.User); result.Err != nil { + return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, "", http.StatusBadRequest) + } else { + user = result.Data.(*model.User) + } + + // Check if this post already exists. + var replies []*model.Post + if result := <-a.Srv.Store.Post().GetPostsCreatedAt(post.ChannelId, *data.CreateAt); result.Err != nil { + return result.Err + } else { + replies = result.Data.([]*model.Post) + } + + var reply *model.Post + for _, r := range replies { + if r.Message == *data.Message { + reply = r + break + } + } + + if reply == nil { + reply = &model.Post{} + } + reply.UserId = user.Id + reply.ChannelId = post.ChannelId + reply.ParentId = post.Id + reply.RootId = post.Id + reply.Message = *data.Message + reply.CreateAt = *data.CreateAt + + if reply.Id == "" { + if result := <-a.Srv.Store.Post().Save(reply); result.Err != nil { + return result.Err + } + } else { + if result := <-a.Srv.Store.Post().Overwrite(reply); result.Err != nil { + return result.Err + } + } + return nil +} + func (a *App) ImportPost(data *PostImportData, dryRun bool) *model.AppError { if err := validatePostImportData(data); err != nil { return err @@ -1114,6 +1224,66 @@ func (a *App) ImportPost(data *PostImportData, dryRun bool) *model.AppError { } } + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + if err := a.ImportReaction(&reaction, post, dryRun); err != nil { + return err + } + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + if err := a.ImportReply(&reply, post, dryRun); err != nil { + return err + } + } + } + + return nil +} + +func validateReactionImportData(data *ReactionImportData, parentCreateAt int64) *model.AppError { + if data.User == nil { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.user_missing.error", nil, "", http.StatusBadRequest) + } + + if data.EmojiName == nil { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.emoji_name_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.EmojiName) > model.EMOJI_NAME_MAX_LENGTH { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.emoji_name_length.error", nil, "", http.StatusBadRequest) + } + + if data.CreateAt == nil { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt == 0 { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt < parentCreateAt { + return model.NewAppError("BulkImport", "app.import.validate_reaction_import_data.create_at_before_parent.error", nil, "", http.StatusBadRequest) + } + + return nil +} + +func validateReplyImportData(data *ReplyImportData, parentCreateAt int64) *model.AppError { + if data.User == nil { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.user_missing.error", nil, "", http.StatusBadRequest) + } + + if data.Message == nil { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.message_missing.error", nil, "", http.StatusBadRequest) + } else if utf8.RuneCountInString(*data.Message) > model.POST_MESSAGE_MAX_RUNES { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.message_length.error", nil, "", http.StatusBadRequest) + } + + if data.CreateAt == nil { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_missing.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt == 0 { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) + } else if *data.CreateAt < parentCreateAt { + return model.NewAppError("BulkImport", "app.import.validate_reply_import_data.create_at_before_parent.error", nil, "", http.StatusBadRequest) + } + return nil } @@ -1142,6 +1312,18 @@ func validatePostImportData(data *PostImportData) *model.AppError { return model.NewAppError("BulkImport", "app.import.validate_post_import_data.create_at_zero.error", nil, "", http.StatusBadRequest) } + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + validateReactionImportData(&reaction, *data.CreateAt) + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + validateReplyImportData(&reply, *data.CreateAt) + } + } + return nil } @@ -1365,6 +1547,22 @@ func (a *App) ImportDirectPost(data *DirectPostImportData, dryRun bool) *model.A } } + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + if err := a.ImportReaction(&reaction, post, dryRun); err != nil { + return err + } + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + if err := a.ImportReply(&reply, post, dryRun); err != nil { + return err + } + } + } + return nil } @@ -1412,6 +1610,18 @@ func validateDirectPostImportData(data *DirectPostImportData) *model.AppError { } } + if data.Reactions != nil { + for _, reaction := range *data.Reactions { + validateReactionImportData(&reaction, *data.CreateAt) + } + } + + if data.Replies != nil { + for _, reply := range *data.Replies { + validateReplyImportData(&reply, *data.CreateAt) + } + } + return nil } diff --git a/app/import_test.go b/app/import_test.go index 630077603..abe32caa8 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -10,6 +10,7 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" + "github.com/mattermost/mattermost-server/utils" ) func ptrStr(s string) *string { @@ -325,6 +326,13 @@ func TestImportValidateUserImportData(t *testing.T) { data.Username = ptrStr("bob") + // Unexisting Picture Image + data.ProfileImage = ptrStr("not-existing-file") + if err := validateUserImportData(&data); err == nil { + t.Fatal("Validation should have failed due to not existing profile image file.") + } + data.ProfileImage = nil + // Invalid Emails data.Email = nil if err := validateUserImportData(&data); err == nil { @@ -360,17 +368,19 @@ func TestImportValidateUserImportData(t *testing.T) { } // Test a valid User with all fields populated. + testsDir, _ := utils.FindDir("tests") data = UserImportData{ - Username: ptrStr("bob"), - Email: ptrStr("bob@example.com"), - AuthService: ptrStr("ldap"), - AuthData: ptrStr("bob"), - Nickname: ptrStr("BobNick"), - FirstName: ptrStr("Bob"), - LastName: ptrStr("Blob"), - Position: ptrStr("The Boss"), - Roles: ptrStr("system_user"), - Locale: ptrStr("en"), + ProfileImage: ptrStr(testsDir + "test.png"), + Username: ptrStr("bob"), + Email: ptrStr("bob@example.com"), + AuthService: ptrStr("ldap"), + AuthData: ptrStr("bob"), + Nickname: ptrStr("BobNick"), + FirstName: ptrStr("Bob"), + LastName: ptrStr("Blob"), + Position: ptrStr("The Boss"), + Roles: ptrStr("system_user"), + Locale: ptrStr("en"), } if err := validateUserImportData(&data); err != nil { t.Fatal("Validation failed but should have been valid.") @@ -563,6 +573,140 @@ func TestImportValidateUserChannelsImportData(t *testing.T) { } } +func TestImportValidateReactionImportData(t *testing.T) { + // Test with minimum required valid properties. + parentCreateAt := model.GetMillis() - 100 + data := ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with missing required properties. + data = ReactionImportData{ + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReactionImportData{ + User: ptrStr("username"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + // Test with invalid emoji name. + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr(strings.Repeat("1234567890", 500)), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to too long emoji name.") + } + + // Test with invalid CreateAt + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(0), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to 0 create-at value.") + } + + data = ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(parentCreateAt - 100), + } + if err := validateReactionImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due parent with newer create-at value.") + } +} + +func TestImportValidateReplyImportData(t *testing.T) { + // Test with minimum required valid properties. + parentCreateAt := model.GetMillis() - 100 + data := ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt); err != nil { + t.Fatal("Validation failed but should have been valid.") + } + + // Test with missing required properties. + data = ReplyImportData{ + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReplyImportData{ + User: ptrStr("username"), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + } + if err := validateReplyImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to missing required property.") + } + + // Test with invalid message. + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr(strings.Repeat("1234567890", 500)), + CreateAt: ptrInt64(model.GetMillis()), + } + if err := validateReplyImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to too long message.") + } + + // Test with invalid CreateAt + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(0), + } + if err := validateReplyImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due to 0 create-at value.") + } + + data = ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(parentCreateAt - 100), + } + if err := validateReplyImportData(&data, parentCreateAt); err == nil { + t.Fatal("Should have failed due parent with newer create-at value.") + } +} + func TestImportValidatePostImportData(t *testing.T) { // Test with minimum required valid properties. @@ -653,12 +797,24 @@ func TestImportValidatePostImportData(t *testing.T) { } // Test with valid all optional parameters. - data = PostImportData{ - Team: ptrStr("teamname"), - Channel: ptrStr("channelname"), + reactions := []ReactionImportData{ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + }} + replies := []ReplyImportData{ReplyImportData{ User: ptrStr("username"), Message: ptrStr("message"), CreateAt: ptrInt64(model.GetMillis()), + }} + data = PostImportData{ + Team: ptrStr("teamname"), + Channel: ptrStr("channelname"), + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + Reactions: &reactions, + Replies: &replies, } if err := validatePostImportData(&data); err != nil { t.Fatal("Should have succeeded.") @@ -961,6 +1117,37 @@ func TestImportValidateDirectPostImportData(t *testing.T) { if err := validateDirectPostImportData(&data); err != nil { t.Fatal(err) } + + // Test with valid all optional parameters. + reactions := []ReactionImportData{ReactionImportData{ + User: ptrStr("username"), + EmojiName: ptrStr("emoji"), + CreateAt: ptrInt64(model.GetMillis()), + }} + replies := []ReplyImportData{ReplyImportData{ + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + }} + data = DirectPostImportData{ + ChannelMembers: &[]string{ + member1, + member2, + }, + FlaggedBy: &[]string{ + member1, + member2, + }, + User: ptrStr("username"), + Message: ptrStr("message"), + CreateAt: ptrInt64(model.GetMillis()), + Reactions: &reactions, + Replies: &replies, + } + + if err := validateDirectPostImportData(&data); err != nil { + t.Fatal(err) + } } func TestImportImportTeam(t *testing.T) { @@ -1298,13 +1485,15 @@ func TestImportImportUser(t *testing.T) { // Do a valid user in apply mode. username := model.NewId() + testsDir, _ := utils.FindDir("tests") data = UserImportData{ - Username: &username, - Email: ptrStr(model.NewId() + "@example.com"), - Nickname: ptrStr(model.NewId()), - FirstName: ptrStr(model.NewId()), - LastName: ptrStr(model.NewId()), - Position: ptrStr(model.NewId()), + ProfileImage: ptrStr(testsDir + "test.png"), + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + Nickname: ptrStr(model.NewId()), + FirstName: ptrStr(model.NewId()), + LastName: ptrStr(model.NewId()), + Position: ptrStr(model.NewId()), } if err := th.App.ImportUser(&data, false); err != nil { t.Fatalf("Should have succeeded to import valid user.") @@ -1354,6 +1543,7 @@ func TestImportImportUser(t *testing.T) { // Alter all the fields of that user. data.Email = ptrStr(model.NewId() + "@example.com") + data.ProfileImage = ptrStr(testsDir + "testgif.gif") data.AuthService = ptrStr("ldap") data.AuthData = &username data.Nickname = ptrStr(model.NewId()) @@ -2189,6 +2379,98 @@ func TestImportImportPost(t *testing.T) { checkPreference(t, th.App, user.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") checkPreference(t, th.App, user2.Id, model.PREFERENCE_CATEGORY_FLAGGED_POST, post.Id, "true") } + + // Post with reaction. + reactionPostTime := hashtagTime + 2 + reactionTime := hashtagTime + 3 + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message with reaction"), + CreateAt: &reactionPostTime, + Reactions: &[]ReactionImportData{{ + User: &user2.Username, + EmojiName: ptrStr("+1"), + CreateAt: &reactionTime, + }}, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 6, team.Id) + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, reactionPostTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id || !post.HasReactions { + t.Fatal("Post properties not as expected") + } + + if result := <-th.App.Srv.Store.Reaction().GetForPost(post.Id, false); result.Err != nil { + t.Fatal("Can't get reaction") + } else if len(result.Data.([]*model.Reaction)) != 1 { + t.Fatal("Invalid number of reactions") + } + } + + // Post with reply. + replyPostTime := hashtagTime + 4 + replyTime := hashtagTime + 5 + data = &PostImportData{ + Team: &teamName, + Channel: &channelName, + User: &username, + Message: ptrStr("Message with reply"), + CreateAt: &replyPostTime, + Replies: &[]ReplyImportData{{ + User: &user2.Username, + Message: ptrStr("Message reply"), + CreateAt: &replyTime, + }}, + } + if err := th.App.ImportPost(data, false); err != nil { + t.Fatalf("Expected success.") + } + AssertAllPostsCount(t, th.App, initialPostCount, 8, team.Id) + + // Check the post values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, replyPostTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + posts := result.Data.([]*model.Post) + if len(posts) != 1 { + t.Fatal("Unexpected number of posts found.") + } + post := posts[0] + if post.Message != *data.Message || post.CreateAt != *data.CreateAt || post.UserId != user.Id { + t.Fatal("Post properties not as expected") + } + + // Check the reply values. + if result := <-th.App.Srv.Store.Post().GetPostsCreatedAt(channel.Id, replyTime); result.Err != nil { + t.Fatal(result.Err.Error()) + } else { + replies := result.Data.([]*model.Post) + if len(replies) != 1 { + t.Fatal("Unexpected number of posts found.") + } + reply := replies[0] + if reply.Message != *(*data.Replies)[0].Message || reply.CreateAt != *(*data.Replies)[0].CreateAt || reply.UserId != user2.Id { + t.Fatal("Post properties not as expected") + } + + if reply.RootId != post.Id { + t.Fatal("Unexpected reply RootId") + } + } + } } func TestImportImportDirectChannel(t *testing.T) { diff --git a/app/user.go b/app/user.go index 493b391ae..64e49e293 100644 --- a/app/user.go +++ b/app/user.go @@ -778,7 +778,10 @@ func (a *App) SetProfileImage(userId string, imageData *multipart.FileHeader) *m return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.open.app_error", nil, err.Error(), http.StatusBadRequest) } defer file.Close() + return a.SetProfileImageFromFile(userId, file) +} +func (a *App) SetProfileImageFromFile(userId string, file multipart.File) *model.AppError { // Decode image config first to check dimensions before loading the whole thing into memory later on config, _, err := image.DecodeConfig(file) if err != nil { -- cgit v1.2.3-1-g7c22