From b29f1cb8443d36d817b2d48bc34eb162974868d8 Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Thu, 15 Nov 2018 21:23:03 +0100 Subject: [PATCH] Add SetProfileImage for plugin API (#9831) --- app/import_functions.go | 2 +- app/plugin_api.go | 15 ++++++ app/plugin_api_test.go | 34 +++++++++++++ app/user.go | 11 +++-- plugin/api.go | 5 ++ plugin/client_rpc_generated.go | 87 ++++++++++++++++++++++------------ plugin/plugintest/api.go | 16 +++++++ 7 files changed, 136 insertions(+), 34 deletions(-) diff --git a/app/import_functions.go b/app/import_functions.go index 81acf0380c..6afa58acd7 100644 --- a/app/import_functions.go +++ b/app/import_functions.go @@ -492,7 +492,7 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { if err != nil { mlog.Error("Unable to open the profile image.", mlog.Any("err", err)) } - if err := a.SetProfileImageFromFile(savedUser.Id, file); err != nil { + if err := a.SetProfileImageFromMultiPartFile(savedUser.Id, file); err != nil { mlog.Error("Unable to set the profile image from a file.", mlog.Any("err", err)) } } diff --git a/app/plugin_api.go b/app/plugin_api.go index 6aae2e618c..68098287f4 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -4,6 +4,7 @@ package app import ( + "bytes" "encoding/json" "fmt" "net/http" @@ -389,6 +390,20 @@ func (api *PluginAPI) GetProfileImage(userId string) ([]byte, *model.AppError) { return data, err } +func (api *PluginAPI) SetProfileImage(userId string, data []byte) *model.AppError { + _, err := api.app.GetUser(userId) + if err != nil { + return err + } + + fileReader := bytes.NewReader(data) + err = api.app.SetProfileImageFromFile(userId, fileReader) + if err != nil { + return err + } + return nil +} + func (api *PluginAPI) GetEmojiList(sortBy string, page, perPage int) ([]*model.Emoji, *model.AppError) { return api.app.GetEmojiList(page, perPage, sortBy) } diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 7311348990..206ba5d1cc 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -4,8 +4,12 @@ package app import ( + "bytes" "encoding/json" "fmt" + "image" + "image/color" + "image/png" "io/ioutil" "os" "path/filepath" @@ -220,6 +224,36 @@ func TestPluginAPIGetProfileImage(t *testing.T) { require.Nil(t, data) } +func TestPluginAPISetProfileImage(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + api := th.SetupPluginAPI() + + // Create an 128 x 128 image + img := image.NewRGBA(image.Rect(0, 0, 128, 128)) + // Draw a red dot at (2, 3) + img.Set(2, 3, color.RGBA{255, 0, 0, 255}) + buf := new(bytes.Buffer) + err := png.Encode(buf, img) + require.Nil(t, err) + dataBytes := buf.Bytes() + + // Set the user profile image + err = api.SetProfileImage(th.BasicUser.Id, dataBytes) + require.Nil(t, err) + + // Get the user profile image to check + imageProfile, err := api.GetProfileImage(th.BasicUser.Id) + require.Nil(t, err) + require.NotEmpty(t, imageProfile) + + colorful := color.NRGBA{255, 0, 0, 255} + byteReader := bytes.NewReader(imageProfile) + img2, _, err2 := image.Decode(byteReader) + require.Nil(t, err2) + require.Equal(t, img2.At(2, 3), colorful) +} + func TestPluginAPIGetPlugins(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/app/user.go b/app/user.go index 05d0fe40f5..5021193270 100644 --- a/app/user.go +++ b/app/user.go @@ -801,10 +801,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) + return a.SetProfileImageFromMultiPartFile(userId, file) } -func (a *App) SetProfileImageFromFile(userId string, file multipart.File) *model.AppError { +func (a *App) SetProfileImageFromMultiPartFile(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 { @@ -816,14 +816,17 @@ func (a *App) SetProfileImageFromFile(userId string, file multipart.File) *model file.Seek(0, 0) + return a.SetProfileImageFromFile(userId, file) +} + +func (a *App) SetProfileImageFromFile(userId string, file io.Reader) *model.AppError { + // Decode image into Image object img, _, err := image.Decode(file) if err != nil { return model.NewAppError("SetProfileImage", "api.user.upload_profile_user.decode.app_error", nil, err.Error(), http.StatusBadRequest) } - file.Seek(0, 0) - orientation, _ := getImageOrientation(file) img = makeImageUpright(img, orientation) diff --git a/plugin/api.go b/plugin/api.go index 33d07c40ac..9f93a31b30 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -264,6 +264,11 @@ type API interface { // Minimum server version: 5.6 GetProfileImage(userId string) ([]byte, *model.AppError) + // SetProfileImage sets a user's profile image. + // + // Minimum server version: 5.6 + SetProfileImage(userId string, data []byte) *model.AppError + // GetEmojiList returns a page of custom emoji on the system. // // The sortBy parameter can be: "name". diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index 2e18934715..3f53f87c85 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -2461,6 +2461,35 @@ func (s *apiRPCServer) GetProfileImage(args *Z_GetProfileImageArgs, returns *Z_G return nil } +type Z_SetProfileImageArgs struct { + A string + B []byte +} + +type Z_SetProfileImageReturns struct { + A *model.AppError +} + +func (g *apiRPCClient) SetProfileImage(userId string, data []byte) *model.AppError { + _args := &Z_SetProfileImageArgs{userId, data} + _returns := &Z_SetProfileImageReturns{} + if err := g.client.Call("Plugin.SetProfileImage", _args, _returns); err != nil { + log.Printf("RPC call to SetProfileImage API failed: %s", err.Error()) + } + return _returns.A +} + +func (s *apiRPCServer) SetProfileImage(args *Z_SetProfileImageArgs, returns *Z_SetProfileImageReturns) error { + if hook, ok := s.impl.(interface { + SetProfileImage(userId string, data []byte) *model.AppError + }); ok { + returns.A = hook.SetProfileImage(args.A, args.B) + } else { + return encodableError(fmt.Errorf("API SetProfileImage called but not implemented.")) + } + return nil +} + type Z_GetEmojiListArgs struct { A string B int @@ -2756,35 +2785,6 @@ func (s *apiRPCServer) GetPlugins(args *Z_GetPluginsArgs, returns *Z_GetPluginsR return nil } -type Z_GetPluginStatusArgs struct { - A string -} - -type Z_GetPluginStatusReturns struct { - A *model.PluginStatus - B *model.AppError -} - -func (g *apiRPCClient) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - _args := &Z_GetPluginStatusArgs{id} - _returns := &Z_GetPluginStatusReturns{} - if err := g.client.Call("Plugin.GetPluginStatus", _args, _returns); err != nil { - log.Printf("RPC call to GetPluginStatus API failed: %s", err.Error()) - } - return _returns.A, _returns.B -} - -func (s *apiRPCServer) GetPluginStatus(args *Z_GetPluginStatusArgs, returns *Z_GetPluginStatusReturns) error { - if hook, ok := s.impl.(interface { - GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) - }); ok { - returns.A, returns.B = hook.GetPluginStatus(args.A) - } else { - return encodableError(fmt.Errorf("API GetPluginStatus called but not implemented.")) - } - return nil -} - type Z_EnablePluginArgs struct { A string } @@ -2869,6 +2869,35 @@ func (s *apiRPCServer) RemovePlugin(args *Z_RemovePluginArgs, returns *Z_RemoveP return nil } +type Z_GetPluginStatusArgs struct { + A string +} + +type Z_GetPluginStatusReturns struct { + A *model.PluginStatus + B *model.AppError +} + +func (g *apiRPCClient) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + _args := &Z_GetPluginStatusArgs{id} + _returns := &Z_GetPluginStatusReturns{} + if err := g.client.Call("Plugin.GetPluginStatus", _args, _returns); err != nil { + log.Printf("RPC call to GetPluginStatus API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) GetPluginStatus(args *Z_GetPluginStatusArgs, returns *Z_GetPluginStatusReturns) error { + if hook, ok := s.impl.(interface { + GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) + }); ok { + returns.A, returns.B = hook.GetPluginStatus(args.A) + } else { + return encodableError(fmt.Errorf("API GetPluginStatus called but not implemented.")) + } + return nil +} + type Z_KVSetArgs struct { A string B []byte diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index d192fe713f..bf9cf4951f 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -1835,6 +1835,22 @@ func (_m *API) SendEphemeralPost(userId string, post *model.Post) *model.Post { return r0 } +// SetProfileImage provides a mock function with given fields: userId, data +func (_m *API) SetProfileImage(userId string, data []byte) *model.AppError { + ret := _m.Called(userId, data) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string, []byte) *model.AppError); ok { + r0 = rf(userId, data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + // UnregisterCommand provides a mock function with given fields: teamId, trigger func (_m *API) UnregisterCommand(teamId string, trigger string) error { ret := _m.Called(teamId, trigger)