github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/remotecluster/sendprofileImage.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package remotecluster
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"mime/multipart"
    13  	"net/http"
    14  	"net/url"
    15  	"path"
    16  	"time"
    17  
    18  	"github.com/masterhung0112/hk_server/v5/model"
    19  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    20  )
    21  
    22  type SendProfileImageResultFunc func(userId string, rc *model.RemoteCluster, resp *Response, err error)
    23  
    24  type sendProfileImageTask struct {
    25  	rc       *model.RemoteCluster
    26  	userID   string
    27  	provider ProfileImageProvider
    28  	f        SendProfileImageResultFunc
    29  }
    30  
    31  type ProfileImageProvider interface {
    32  	GetProfileImage(user *model.User) ([]byte, bool, *model.AppError)
    33  }
    34  
    35  // SendProfileImage asynchronously sends a user's profile image to a remote cluster.
    36  //
    37  // `ctx` determines behaviour when the outbound queue is full. A timeout or deadline context will return a
    38  // BufferFullError if the task cannot be enqueued before the timeout. A background context will block indefinitely.
    39  //
    40  // Nil or error return indicates success or failure of task enqueue only.
    41  //
    42  // An optional callback can be provided that receives the response from the remote cluster. The `err` provided to the
    43  // callback is regarding image delivery only. The `resp` contains the decoded bytes returned from the remote.
    44  // If a callback is provided it should return quickly.
    45  func (rcs *Service) SendProfileImage(ctx context.Context, userID string, rc *model.RemoteCluster, provider ProfileImageProvider, f SendProfileImageResultFunc) error {
    46  	task := sendProfileImageTask{
    47  		rc:       rc,
    48  		userID:   userID,
    49  		provider: provider,
    50  		f:        f,
    51  	}
    52  	return rcs.enqueueTask(ctx, rc.RemoteId, task)
    53  }
    54  
    55  // sendProfileImage is called when a sendProfileImageTask is popped from the send channel.
    56  func (rcs *Service) sendProfileImage(task sendProfileImageTask) {
    57  	err := rcs.sendProfileImageToRemote(SendTimeout, task)
    58  	var response Response
    59  
    60  	if err != nil {
    61  		rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceError, "Remote Cluster send profile image failed",
    62  			mlog.String("remote", task.rc.DisplayName),
    63  			mlog.String("UserId", task.userID),
    64  			mlog.Err(err),
    65  		)
    66  		response.Status = ResponseStatusFail
    67  		response.Err = err.Error()
    68  	} else {
    69  		rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceDebug, "Remote Cluster profile image sent successfully",
    70  			mlog.String("remote", task.rc.DisplayName),
    71  			mlog.String("UserId", task.userID),
    72  		)
    73  		response.Status = ResponseStatusOK
    74  	}
    75  
    76  	// If callback provided then call it with the results.
    77  	if task.f != nil {
    78  		task.f(task.userID, task.rc, &response, err)
    79  	}
    80  }
    81  
    82  func (rcs *Service) sendProfileImageToRemote(timeout time.Duration, task sendProfileImageTask) error {
    83  	rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceDebug, "sending profile image to remote...",
    84  		mlog.String("remote", task.rc.DisplayName),
    85  		mlog.String("UserId", task.userID),
    86  	)
    87  
    88  	user, err := rcs.server.GetStore().User().Get(context.Background(), task.userID)
    89  	if err != nil {
    90  		return fmt.Errorf("error fetching user while sending profile image to remote %s: %w", task.rc.RemoteId, err)
    91  	}
    92  
    93  	img, _, appErr := task.provider.GetProfileImage(user) // get Reader for the file
    94  	if appErr != nil {
    95  		return fmt.Errorf("error fetching profile image for user (%s) while sending to remote %s: %w", task.userID, task.rc.RemoteId, appErr)
    96  	}
    97  
    98  	u, err := url.Parse(task.rc.SiteURL)
    99  	if err != nil {
   100  		return fmt.Errorf("invalid siteURL while sending file to remote %s: %w", task.rc.RemoteId, err)
   101  	}
   102  	u.Path = path.Join(u.Path, model.API_URL_SUFFIX, "remotecluster", task.userID, "image")
   103  
   104  	body := &bytes.Buffer{}
   105  	writer := multipart.NewWriter(body)
   106  
   107  	part, err := writer.CreateFormFile("image", "profile.png")
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	if _, err = io.Copy(part, bytes.NewBuffer(img)); err != nil {
   113  		return err
   114  	}
   115  
   116  	if err = writer.Close(); err != nil {
   117  		return err
   118  	}
   119  
   120  	req, err := http.NewRequest("POST", u.String(), body)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	req.Header.Set("Content-Type", writer.FormDataContentType())
   125  	req.Header.Set(model.HEADER_REMOTECLUSTER_ID, task.rc.RemoteId)
   126  	req.Header.Set(model.HEADER_REMOTECLUSTER_TOKEN, task.rc.RemoteToken)
   127  
   128  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   129  	defer cancel()
   130  
   131  	resp, err := rcs.httpClient.Do(req.WithContext(ctx))
   132  	if err != nil {
   133  		return err
   134  	}
   135  	defer resp.Body.Close()
   136  
   137  	_, err = ioutil.ReadAll(resp.Body)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	if resp.StatusCode != http.StatusOK {
   143  		return fmt.Errorf("unexpected response: %d - %s", resp.StatusCode, resp.Status)
   144  	}
   145  	return nil
   146  }